Move the threepid stuff to settings

This commit is contained in:
James Graham
2025-04-12 17:52:12 +01:00
parent 3a4bc18d45
commit 09cb2bd261
8 changed files with 4 additions and 6 deletions

View File

@@ -46,18 +46,22 @@ ecm_add_qml_module(Settings GENERATE_PLUGIN_SOURCE
RoomProfile.qml
SOURCES
colorschemer.cpp
threepidaddhelper.cpp
threepidbindhelper.cpp
models/accountemoticonmodel.cpp
models/devicesmodel.cpp
models/devicesproxymodel.cpp
models/emoticonfiltermodel.cpp
models/permissionsmodel.cpp
models/pushrulemodel.cpp
models/threepidmodel.cpp
)
target_include_directories(Settings PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(Settings PRIVATE
KF6::ColorScheme
KF6::I18n
QuotientQt6
LibNeoChat
)

View File

@@ -0,0 +1,171 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "threepidmodel.h"
#include <QCryptographicHash>
#include <QNetworkReply>
#include <Quotient/csapi/openid.h>
#include <Quotient/networkaccessmanager.h>
#include "neochatconnection.h"
using namespace Qt::StringLiterals;
ThreePIdModel::ThreePIdModel(QObject *parent)
: QAbstractListModel(parent)
{
}
NeoChatConnection *ThreePIdModel::connection() const
{
return m_connection;
}
void ThreePIdModel::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
if (m_connection != nullptr) {
m_connection->disconnect(this);
}
m_connection = connection;
if (m_connection) {
connect(m_connection, &NeoChatConnection::stateChanged, this, [this]() {
refreshModel();
});
refreshModel();
}
Q_EMIT connectionChanged();
}
QVariant ThreePIdModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
if (index.row() >= rowCount()) {
qDebug() << "ThreePIdModel, something's wrong: index.row() >= m_threePIds.count()";
return {};
}
if (role == AddressRole) {
return m_threePIds.at(index.row()).address;
}
if (role == MediumRole) {
return m_threePIds.at(index.row()).medium;
}
if (role == IsBoundRole) {
return m_bindings.contains(m_threePIds.at(index.row()).address);
}
return {};
}
int ThreePIdModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_threePIds.count();
}
QHash<int, QByteArray> ThreePIdModel::roleNames() const
{
return {
{AddressRole, QByteArrayLiteral("address")},
{MediumRole, QByteArrayLiteral("medium")},
{IsBoundRole, QByteArrayLiteral("isBound")},
};
}
void ThreePIdModel::refreshModel()
{
if (m_connection != nullptr && m_connection->isLoggedIn()) {
if (m_job.isRunning()) {
m_job.cancel();
}
m_job = m_connection->callApi<Quotient::GetAccount3PIDsJob>();
connect(m_job, &Quotient::BaseJob::success, this, [this]() {
beginResetModel();
m_threePIds = m_job->threepids();
endResetModel();
refreshBindStatus();
});
}
}
void ThreePIdModel::refreshBindStatus()
{
if (m_connection == nullptr || !m_connection->hasIdentityServer()) {
return;
}
const auto openIdJob = m_connection->callApi<Quotient::RequestOpenIdTokenJob>(m_connection->userId());
connect(openIdJob, &Quotient::BaseJob::success, this, [this, openIdJob]() {
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/account/register"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
return;
}
QNetworkRequest request(requestUrl);
auto newRequest = Quotient::NetworkAccessManager::instance()->post(request, QJsonDocument(openIdJob->jsonData()).toJson());
connect(newRequest, &QNetworkReply::finished, this, [this, newRequest]() {
QJsonObject replyJson = QJsonDocument::fromJson(newRequest->readAll()).object();
const auto identityServerToken = replyJson["token"_L1].toString();
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/hash_details"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
return;
}
QNetworkRequest hashRequest(requestUrl);
hashRequest.setRawHeader("Authorization", "Bearer " + identityServerToken.toLatin1());
auto hashReply = Quotient::NetworkAccessManager::instance()->get(hashRequest);
connect(hashReply, &QNetworkReply::finished, this, [this, identityServerToken, hashReply]() {
QJsonObject replyJson = QJsonDocument::fromJson(hashReply->readAll()).object();
const auto lookupPepper = replyJson["lookup_pepper"_L1].toString();
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/lookup"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
return;
}
QNetworkRequest lookupRequest(requestUrl);
lookupRequest.setRawHeader("Authorization", "Bearer " + identityServerToken.toLatin1());
QJsonObject requestData = {
{"algorithm"_L1, "none"_L1},
{"pepper"_L1, lookupPepper},
};
QJsonArray idLookups;
for (const auto &id : m_threePIds) {
idLookups += u"%1 %2"_s.arg(id.address, id.medium);
}
requestData["addresses"_L1] = idLookups;
auto lookupReply = Quotient::NetworkAccessManager::instance()->post(lookupRequest, QJsonDocument(requestData).toJson(QJsonDocument::Compact));
connect(lookupReply, &QNetworkReply::finished, this, [this, lookupReply]() {
beginResetModel();
m_bindings.clear();
QJsonObject mappings = QJsonDocument::fromJson(lookupReply->readAll()).object()["mappings"_L1].toObject();
for (const auto &id : mappings.keys()) {
if (mappings[id] == m_connection->userId()) {
m_bindings += id.section(u' ', 0, 0);
}
}
endResetModel();
});
});
});
});
}
#include "moc_threepidmodel.cpp"

View File

@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/csapi/administrative_contact.h>
#include <Quotient/jobs/jobhandle.h>
class NeoChatConnection;
/**
* @class ThreePIdModel
*
* This class defines the model for visualising an account's 3PIDs.
*/
class ThreePIdModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current connection for the model to use.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
public:
/**
* @brief Defines the model roles.
*/
enum Roles {
AddressRole = Qt::DisplayRole, /**< The third-party identifier address. */
MediumRole, /**< The medium of the third-party identifier. One of: [email, msisdn]. */
IsBoundRole, /**< Whether the 3PID is bound to the current identity server. */
};
Q_ENUM(Roles)
explicit ThreePIdModel(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa EventRoles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void refreshModel();
Q_SIGNALS:
void connectionChanged();
private:
QPointer<NeoChatConnection> m_connection;
QVector<Quotient::GetAccount3PIDsJob::ThirdPartyIdentifier> m_threePIds;
Quotient::JobHandle<Quotient::GetAccount3PIDsJob> m_job;
QList<QString> m_bindings;
void refreshBindStatus();
};

View File

@@ -0,0 +1,210 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "threepidaddhelper.h"
#include <Quotient/converters.h>
#include <Quotient/csapi/administrative_contact.h>
#include <Quotient/csapi/definitions/auth_data.h>
#include <Quotient/csapi/definitions/request_msisdn_validation.h>
#include "neochatconnection.h"
using namespace Qt::StringLiterals;
using namespace Quotient;
ThreePIdAddHelper::ThreePIdAddHelper(QObject *parent)
: QObject(parent)
{
}
NeoChatConnection *ThreePIdAddHelper::connection() const
{
return m_connection;
}
void ThreePIdAddHelper::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
m_connection = connection;
Q_EMIT connectionChanged();
}
QString ThreePIdAddHelper::medium() const
{
return m_medium;
}
void ThreePIdAddHelper::setMedium(const QString &medium)
{
if (m_medium == medium) {
return;
}
m_medium = medium;
Q_EMIT mediumChanged();
}
QString ThreePIdAddHelper::newId() const
{
return m_newId;
}
void ThreePIdAddHelper::setNewId(const QString &newId)
{
if (newId == m_newId) {
return;
}
m_newId = newId;
Q_EMIT newIdChanged();
m_newIdSecret.clear();
m_newIdSid.clear();
m_newIdStatus = Ready;
Q_EMIT newIdStatusChanged();
}
QString ThreePIdAddHelper::newCountryCode() const
{
return m_newCountryCode;
}
void ThreePIdAddHelper::setNewCountryCode(const QString &newCountryCode)
{
if (newCountryCode == m_newCountryCode) {
return;
}
m_newCountryCode = newCountryCode;
Q_EMIT newCountryCodeChanged();
m_newIdSecret.clear();
m_newIdSid.clear();
m_newIdStatus = Ready;
Q_EMIT newIdStatusChanged();
}
void ThreePIdAddHelper::initiateNewIdAdd()
{
if (m_newId.isEmpty()) {
return;
}
if (m_medium == u"email"_s) {
emailTokenJob();
} else {
msisdnTokenJob();
}
}
void ThreePIdAddHelper::emailTokenJob()
{
m_newIdSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64());
Quotient::EmailValidationData data;
data.email = m_newId;
data.clientSecret = m_newIdSecret;
data.sendAttempt = 0;
const auto job = m_connection->callApi<Quotient::RequestTokenTo3PIDEmailJob>(data);
connect(job, &Quotient::BaseJob::finished, this, &ThreePIdAddHelper::tokenJobFinished);
}
void ThreePIdAddHelper::msisdnTokenJob()
{
m_newIdSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64());
Quotient::MsisdnValidationData data;
data.country = m_newCountryCode;
data.phoneNumber = m_newId;
data.clientSecret = m_newIdSecret;
data.sendAttempt = 0;
const auto job = m_connection->callApi<Quotient::RequestTokenTo3PIDMSISDNJob>(data);
connect(job, &Quotient::BaseJob::finished, this, &ThreePIdAddHelper::tokenJobFinished);
}
void ThreePIdAddHelper::tokenJobFinished(Quotient::BaseJob *job)
{
if (job->status() == Quotient::BaseJob::Success) {
m_newIdSid = job->jsonData()["sid"_L1].toString();
m_newIdStatus = Verification;
Q_EMIT newIdStatusChanged();
return;
}
m_newIdStatus = Invalid;
Q_EMIT newIdStatusChanged();
}
ThreePIdAddHelper::ThreePIdStatus ThreePIdAddHelper::newIdStatus() const
{
return m_newIdStatus;
}
void ThreePIdAddHelper::finalizeNewIdAdd(const QString &password)
{
const auto job = m_connection->callApi<Add3PIDJob>(m_newIdSecret, m_newIdSid);
connect(job, &Quotient::BaseJob::result, this, [this, job, password] {
m_newIdStatus = Authentication;
Q_EMIT newIdStatusChanged();
if (static_cast<Quotient::BaseJob::StatusCode>(job->error()) == Quotient::BaseJob::Unauthorised) {
QJsonObject replyData = job->jsonData();
AuthenticationData authData;
authData.session = replyData["session"_L1].toString();
authData.authInfo["password"_L1] = password;
authData.type = "m.login.password"_L1;
authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, m_connection->userId()}};
const auto innerJob = m_connection->callApi<Add3PIDJob>(m_newIdSecret, m_newIdSid, authData);
connect(innerJob, &Quotient::BaseJob::success, this, [this]() {
m_newIdSecret.clear();
m_newIdSid.clear();
m_newIdStatus = Success;
Q_EMIT newIdStatusChanged();
Q_EMIT threePIdAdded();
});
connect(innerJob, &Quotient::BaseJob::failure, this, [innerJob, this]() {
if (innerJob->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1) {
m_newIdStatus = AuthFailure;
Q_EMIT newIdStatusChanged();
} else if (innerJob->jsonData()["errcode"_L1] == "M_THREEPID_AUTH_FAILED"_L1) {
m_newIdStatus = VerificationFailure;
Q_EMIT newIdStatusChanged();
} else {
m_newIdStatus = Other;
Q_EMIT newIdStatusChanged();
}
});
}
});
}
void ThreePIdAddHelper::remove3PId(const QString &threePId, const QString &type)
{
const auto job = m_connection->callApi<Quotient::Delete3pidFromAccountJob>(type, threePId);
connect(job, &Quotient::BaseJob::success, this, [this]() {
Q_EMIT threePIdRemoved();
});
}
void ThreePIdAddHelper::unbind3PId(const QString &threePId, const QString &type)
{
const auto job = m_connection->callApi<Quotient::Unbind3pidFromAccountJob>(type, threePId);
connect(job, &Quotient::BaseJob::success, this, [this]() {
Q_EMIT threePIdUnbound();
});
}
void ThreePIdAddHelper::back()
{
switch (m_newIdStatus) {
case Verification:
case Authentication:
case AuthFailure:
case VerificationFailure:
m_newIdStatus = Ready;
Q_EMIT newIdStatusChanged();
return;
default:
return;
}
}
#include "moc_threepidaddhelper.cpp"

View File

@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/jobs/basejob.h>
class NeoChatConnection;
/**
* @class ThreePIdAddHelper
*
* This class is designed to help the process of adding a new 3PID to the account.
* It will manage the various stages of verification and authentication.
*/
class ThreePIdAddHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The connection to add a 3PID to.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The type of 3PID being added.
*
* email or msisdn.
*/
Q_PROPERTY(QString medium READ medium WRITE setMedium NOTIFY mediumChanged)
/**
* @brief The 3PID to add.
*
* Email or phone number depending on type.
*/
Q_PROPERTY(QString newId READ newId WRITE setNewId NOTIFY newIdChanged)
/**
* @brief The country code if a phone number is being added.
*/
Q_PROPERTY(QString newCountryCode READ newCountryCode WRITE setNewCountryCode NOTIFY newCountryCodeChanged)
/**
* @brief The current status.
*
* @sa ThreePIdStatus
*/
Q_PROPERTY(ThreePIdStatus newIdStatus READ newIdStatus NOTIFY newIdStatusChanged)
public:
/**
* @brief Defines the current status for adding a 3PID.
*/
enum ThreePIdStatus {
Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new 3PID. */
Verification, /**< The request to verify the new 3PID has been sent. */
Authentication, /**< The user needs to authenticate. */
Success, /**< The 3PID has been successfully added. */
Invalid, /**< The 3PID can't be used. */
AuthFailure, /**< The authentication was wrong. */
VerificationFailure, /**< The verification has not been completed. */
Other, /**< An unknown problem occurred. */
};
Q_ENUM(ThreePIdStatus)
explicit ThreePIdAddHelper(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString medium() const;
void setMedium(const QString &medium);
[[nodiscard]] QString newId() const;
void setNewId(const QString &newEmail);
[[nodiscard]] QString newCountryCode() const;
void setNewCountryCode(const QString &newCountryCode);
/**
* @brief Start the process to add the new 3PID.
*
* This will start the process of verifying the 3PID credentials that have been given.
*/
Q_INVOKABLE void initiateNewIdAdd();
[[nodiscard]] ThreePIdStatus newIdStatus() const;
/**
* @brief Finalize the process of adding the new 3PID.
*
* @param password the user's password to authenticate the addition.
*/
Q_INVOKABLE void finalizeNewIdAdd(const QString &password);
/**
* @brief Remove the given 3PID.
*/
Q_INVOKABLE void remove3PId(const QString &threePId, const QString &type);
/**
* @brief Remove the given 3PID.
*/
Q_INVOKABLE void unbind3PId(const QString &threePId, const QString &type);
/**
* @brief Go back a step in the process.
*/
Q_INVOKABLE void back();
Q_SIGNALS:
void connectionChanged();
void mediumChanged();
void newIdChanged();
void newCountryCodeChanged();
void newEmailSessionStartedChanged();
void newIdStatusChanged();
/**
* @brief A 3PID has been added.
*/
void threePIdAdded();
/**
* @brief A 3PID has been removed.
*/
void threePIdRemoved();
/**
* @brief A 3PID has been unbound.
*/
void threePIdUnbound();
private:
QPointer<NeoChatConnection> m_connection;
QString m_medium = QString();
ThreePIdStatus m_newIdStatus = Ready;
QString m_newId = QString();
QString m_newCountryCode = QString();
QString m_newIdSecret = QString();
QString m_newIdSid = QString();
void emailTokenJob();
void msisdnTokenJob();
void tokenJobFinished(Quotient::BaseJob *job);
};

View File

@@ -0,0 +1,234 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "threepidbindhelper.h"
#include <QNetworkReply>
#include <Quotient/converters.h>
#include <Quotient/csapi/administrative_contact.h>
#include <Quotient/csapi/definitions/auth_data.h>
#include <Quotient/csapi/definitions/request_msisdn_validation.h>
#include <Quotient/csapi/openid.h>
#include <Quotient/networkaccessmanager.h>
#include <KLocalizedString>
#include "neochatconnection.h"
using namespace Qt::StringLiterals;
ThreePIdBindHelper::ThreePIdBindHelper(QObject *parent)
: QObject(parent)
{
}
NeoChatConnection *ThreePIdBindHelper::connection() const
{
return m_connection;
}
void ThreePIdBindHelper::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
m_connection = connection;
Q_EMIT connectionChanged();
}
QString ThreePIdBindHelper::medium() const
{
return m_medium;
}
void ThreePIdBindHelper::setMedium(const QString &medium)
{
if (m_medium == medium) {
return;
}
m_medium = medium;
Q_EMIT mediumChanged();
}
QString ThreePIdBindHelper::newId() const
{
return m_newId;
}
void ThreePIdBindHelper::setNewId(const QString &newId)
{
if (newId == m_newId) {
return;
}
m_newId = newId;
Q_EMIT newIdChanged();
m_newIdSecret.clear();
m_newIdSid.clear();
m_identityServerToken.clear();
m_bindStatus = Ready;
Q_EMIT bindStatusChanged();
}
QString ThreePIdBindHelper::newCountryCode() const
{
return m_newCountryCode;
}
void ThreePIdBindHelper::setNewCountryCode(const QString &newCountryCode)
{
if (newCountryCode == m_newCountryCode) {
return;
}
m_newCountryCode = newCountryCode;
Q_EMIT newCountryCodeChanged();
m_newIdSecret.clear();
m_newIdSid.clear();
m_identityServerToken.clear();
m_bindStatus = Ready;
Q_EMIT bindStatusChanged();
}
void ThreePIdBindHelper::initiateNewIdBind()
{
if (m_newId.isEmpty() || m_connection == nullptr || !m_connection->hasIdentityServer()) {
return;
}
const auto openIdJob = m_connection->callApi<Quotient::RequestOpenIdTokenJob>(m_connection->userId());
connect(openIdJob, &Quotient::BaseJob::success, this, [this, openIdJob]() {
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/account/register"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
m_bindStatus = AuthFailure;
Q_EMIT bindStatusChanged();
return;
}
QNetworkRequest request(requestUrl);
auto newRequest = Quotient::NetworkAccessManager::instance()->post(request, QJsonDocument(openIdJob->jsonData()).toJson());
connect(newRequest, &QNetworkReply::finished, this, [this, newRequest]() {
QJsonObject replyJson = parseJson(newRequest->readAll());
m_identityServerToken = replyJson["token"_L1].toString();
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/validate/email/requestToken"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
m_bindStatus = AuthFailure;
Q_EMIT bindStatusChanged();
return;
}
QNetworkRequest validationRequest(requestUrl);
validationRequest.setRawHeader("Authorization", "Bearer " + m_identityServerToken.toLatin1());
auto tokenRequest = Quotient::NetworkAccessManager::instance()->post(validationRequest, validationRequestData());
connect(tokenRequest, &QNetworkReply::finished, this, [this, tokenRequest]() {
tokenRequestFinished(tokenRequest);
});
});
});
}
QByteArray ThreePIdBindHelper::validationRequestData()
{
m_newIdSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64());
QJsonObject requestData = {
{"client_secret"_L1, m_newIdSecret},
{"send_attempt"_L1, 0},
};
if (m_medium == u"email"_s) {
requestData["email"_L1] = m_newId;
} else {
requestData["phone_number"_L1] = m_newId;
requestData["country"_L1] = m_newCountryCode;
}
return QJsonDocument(requestData).toJson();
}
void ThreePIdBindHelper::tokenRequestFinished(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
return;
}
QJsonObject replyJson = parseJson(reply->readAll());
m_newIdSid = replyJson["sid"_L1].toString();
if (m_newIdSid.isEmpty()) {
m_bindStatus = Invalid;
Q_EMIT bindStatusChanged();
} else {
m_bindStatus = Verification;
Q_EMIT bindStatusChanged();
}
}
ThreePIdBindHelper::ThreePIdStatus ThreePIdBindHelper::bindStatus() const
{
return m_bindStatus;
}
QString ThreePIdBindHelper::bindStatusString() const
{
switch (m_bindStatus) {
case Verification:
return i18n("%1. Please follow the instructions there and then click the button above",
m_medium == u"email"_s ? i18n("We've sent you an email") : i18n("We've sent you a text message"));
case Invalid:
return m_medium == u"email"_s ? i18n("The entered email is not valid") : i18n("The entered phone number is not valid");
case VerificationFailure:
return m_medium == u"email"_s
? i18n("The email has not been verified. Please go to the email and follow the instructions there and then click the button above")
: i18n("The phone number has not been verified. Please go to the text message and follow the instructions there and then click the button above");
default:
return {};
}
}
void ThreePIdBindHelper::finalizeNewIdBind()
{
const auto job = m_connection->callApi<Quotient::Bind3PIDJob>(m_newIdSecret, m_connection->identityServer().host(), m_identityServerToken, m_newIdSid);
connect(job, &Quotient::BaseJob::success, this, [this] {
m_bindStatus = Success;
Q_EMIT bindStatusChanged();
Q_EMIT threePIdBound();
});
connect(job, &Quotient::BaseJob::failure, this, [this, job]() {
if (job->jsonData()["errcode"_L1] == "M_SESSION_NOT_VALIDATED"_L1) {
m_bindStatus = VerificationFailure;
Q_EMIT bindStatusChanged();
} else {
m_bindStatus = Other;
Q_EMIT bindStatusChanged();
}
});
}
void ThreePIdBindHelper::unbind3PId(const QString &threePId, const QString &type)
{
const auto job = m_connection->callApi<Quotient::Unbind3pidFromAccountJob>(type, threePId);
connect(job, &Quotient::BaseJob::success, this, [this]() {
cancel();
Q_EMIT threePIdUnbound();
});
}
void ThreePIdBindHelper::cancel()
{
m_newIdSecret.clear();
m_newIdSid.clear();
m_identityServerToken.clear();
m_bindStatus = Ready;
Q_EMIT bindStatusChanged();
}
QJsonObject ThreePIdBindHelper::parseJson(const QByteArray &json)
{
const auto document = QJsonDocument::fromJson(json);
return document.object();
}
#include "moc_threepidbindhelper.cpp"

View File

@@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/jobs/basejob.h>
class NeoChatConnection;
/**
* @class ThreePIdBindHelper
*
* This class is designed to help the process of bindind a 3PID to an identity server.
* It will manage the various stages of verification and authentication.
*/
class ThreePIdBindHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The connection to bind a 3PID for.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The type of 3PID being bound.
*
* email or msisdn.
*/
Q_PROPERTY(QString medium READ medium WRITE setMedium NOTIFY mediumChanged)
/**
* @brief The 3PID to bind.
*
* Email or phone number depending on type.
*/
Q_PROPERTY(QString newId READ newId WRITE setNewId NOTIFY newIdChanged)
/**
* @brief The country code if a phone number is being bound.
*/
Q_PROPERTY(QString newCountryCode READ newCountryCode WRITE setNewCountryCode NOTIFY newCountryCodeChanged)
/**
* @brief The current status.
*
* @sa ThreePIdStatus
*/
Q_PROPERTY(ThreePIdStatus bindStatus READ bindStatus NOTIFY bindStatusChanged)
/**
* @brief The current status as a string.
*
* @sa ThreePIdStatus
*/
Q_PROPERTY(QString bindStatusString READ bindStatusString NOTIFY bindStatusChanged)
public:
/**
* @brief Defines the current status for binding a 3PID.
*/
enum ThreePIdStatus {
Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new 3PID. */
Verification, /**< The request to verify the new 3PID has been sent. */
Authentication, /**< The user needs to authenticate. */
Success, /**< The 3PID has been successfully added. */
Invalid, /**< The 3PID can't be used. */
AuthFailure, /**< The authentication was wrong. */
VerificationFailure, /**< The verification has not been completed. */
Other, /**< An unknown problem occurred. */
};
Q_ENUM(ThreePIdStatus)
explicit ThreePIdBindHelper(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString medium() const;
void setMedium(const QString &medium);
[[nodiscard]] QString newId() const;
void setNewId(const QString &newEmail);
[[nodiscard]] QString newCountryCode() const;
void setNewCountryCode(const QString &newCountryCode);
/**
* @brief Start the process to bind the new 3PID.
*
* This will start the process of verifying the 3PID credentials that have been given.
* Will fail if no identity server is configured.
*/
Q_INVOKABLE void initiateNewIdBind();
[[nodiscard]] ThreePIdStatus bindStatus() const;
[[nodiscard]] QString bindStatusString() const;
/**
* @brief Finalize the process of binding the new 3PID.
*
* Will fail if the user hasn't completed the verification with the identity
* server.
*/
Q_INVOKABLE void finalizeNewIdBind();
/**
* @brief Unbind the given 3PID.
*/
Q_INVOKABLE void unbind3PId(const QString &threePId, const QString &type);
/**
* @brief Cancel the process.
*/
Q_INVOKABLE void cancel();
Q_SIGNALS:
void connectionChanged();
void mediumChanged();
void newIdChanged();
void newCountryCodeChanged();
void newEmailSessionStartedChanged();
void bindStatusChanged();
/**
* @brief A 3PID has been unbound.
*/
void threePIdBound();
/**
* @brief A 3PID has been unbound.
*/
void threePIdUnbound();
private:
QPointer<NeoChatConnection> m_connection;
QString m_medium = QString();
ThreePIdStatus m_bindStatus = Ready;
QString m_newId = QString();
QString m_newCountryCode = QString();
QString m_newIdSecret = QString();
QString m_newIdSid = QString();
QString m_identityServerToken = QString();
QByteArray validationRequestData();
void tokenRequestFinished(QNetworkReply *reply);
static QJsonObject parseJson(const QByteArray &json);
};