File indexing completed on 2024-09-15 04:28:35
0001 // SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org> 0002 // SPDX-License-Identifier: GPL-2.0-or-later 0003 0004 #include "registration.h" 0005 0006 #include <QTcpServer> 0007 #include <QTcpSocket> 0008 #include <QThread> 0009 0010 #include <Quotient/csapi/registration.h> 0011 #include <Quotient/qt_connection_util.h> 0012 #include <Quotient/settings.h> 0013 0014 #include "controller.h" 0015 #include "login.h" 0016 0017 #include <KLocalizedString> 0018 0019 using namespace Quotient; 0020 0021 Registration::Registration() 0022 { 0023 auto server = new QTcpServer(this); 0024 server->listen(QHostAddress("127.0.0.1"_ls), 20847); 0025 connect(server, &QTcpServer::newConnection, this, [this, server]() { 0026 auto conn = server->nextPendingConnection(); 0027 connect(conn, &QIODevice::readyRead, this, [this, conn]() { 0028 auto code = 0029 "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 0030 .arg(m_recaptchaSiteKey); 0031 conn->write(code.toLatin1().data(), code.length()); 0032 conn->close(); 0033 }); 0034 }); 0035 0036 connect(this, &Registration::homeserverChanged, this, &Registration::testHomeserver); 0037 connect(this, &Registration::usernameChanged, this, &Registration::testUsername); 0038 } 0039 0040 void Registration::setRecaptchaResponse(const QString &recaptchaResponse) 0041 { 0042 m_recaptchaResponse = recaptchaResponse; 0043 Q_EMIT recaptchaResponseChanged(); 0044 registerAccount(); 0045 } 0046 0047 QString Registration::recaptchaResponse() const 0048 { 0049 return m_recaptchaResponse; 0050 } 0051 0052 void Registration::setRecaptchaSiteKey(const QString &recaptchaSiteKey) 0053 { 0054 m_recaptchaSiteKey = recaptchaSiteKey; 0055 Q_EMIT recaptchaSiteKeyChanged(); 0056 } 0057 0058 QString Registration::recaptchaSiteKey() const 0059 { 0060 return m_recaptchaSiteKey; 0061 } 0062 0063 void Registration::registerAccount() 0064 { 0065 setStatus(Working); 0066 Omittable<QJsonObject> authData = none; 0067 if (nextStep() == "m.login.recaptcha"_ls) { 0068 authData = QJsonObject{ 0069 {"type"_ls, "m.login.recaptcha"_ls}, 0070 {"response"_ls, m_recaptchaResponse}, 0071 {"session"_ls, m_session}, 0072 }; 0073 } else if (nextStep() == "m.login.terms"_ls) { 0074 authData = QJsonObject{ 0075 {"type"_ls, "m.login.terms"_ls}, 0076 {"session"_ls, m_session}, 0077 }; 0078 } else if (nextStep() == "m.login.email.identity"_ls) { 0079 authData = QJsonObject{ 0080 {"type"_ls, "m.login.email.identity"_ls}, 0081 {"threepid_creds"_ls, 0082 QJsonObject{ 0083 {"sid"_ls, m_sid}, 0084 {"client_secret"_ls, m_emailSecret}, 0085 }}, 0086 {"session"_ls, m_session}, 0087 }; 0088 } 0089 auto job = m_connection->callApi<NeoChatRegisterJob>("user"_ls, authData, m_username, m_password, QString(), QString(), true); 0090 connect(job, &BaseJob::result, this, [this, job]() { 0091 if (job->status() == BaseJob::Success) { 0092 setNextStep("loading"_ls); 0093 auto connection = new NeoChatConnection(this); 0094 auto matrixId = "@%1:%2"_ls.arg(m_username, m_homeserver); 0095 connection->resolveServer(matrixId); 0096 0097 auto displayName = "NeoChat %1 %2 %3 %4"_ls.arg(QSysInfo::machineHostName(), 0098 QSysInfo::productType(), 0099 QSysInfo::productVersion(), 0100 QSysInfo::currentCpuArchitecture()); 0101 connection->loginWithPassword(matrixId, m_password, displayName); 0102 0103 connect(connection, &Connection::connected, this, [this, displayName, connection] { 0104 AccountSettings account(connection->userId()); 0105 account.setKeepLoggedIn(true); 0106 account.setHomeserver(connection->homeserver()); 0107 account.setDeviceId(connection->deviceId()); 0108 account.setDeviceName(displayName); 0109 if (!Controller::instance().saveAccessTokenToKeyChain(account, connection->accessToken())) { 0110 qWarning() << "Couldn't save access token"; 0111 } 0112 account.sync(); 0113 Controller::instance().addConnection(connection); 0114 Controller::instance().setActiveConnection(connection); 0115 connectSingleShot(connection, &Connection::syncDone, this, []() { 0116 Q_EMIT LoginHelper::instance().loaded(); 0117 }); 0118 m_connection = nullptr; 0119 }); 0120 0121 return; 0122 } 0123 const auto &data = job->jsonData(); 0124 m_session = data["session"_ls].toString(); 0125 const auto ¶ms = data["params"_ls].toObject(); 0126 0127 // I'm not motivated enough to figure out how we should handle the flow stuff, so: 0128 // 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. 0129 // Otherwise, we're using the first flow. 0130 auto selectedFlow = data["flows"_ls].toArray()[0].toObject()["stages"_ls].toArray(); 0131 for (const auto &flow : data["flows"_ls].toArray()) { 0132 if (flow.toObject()["stages"_ls].toArray().contains("m.login.email.identity"_ls)) { 0133 selectedFlow = flow.toObject()["stages"_ls].toArray(); 0134 } 0135 } 0136 0137 setNextStep(selectedFlow[data["completed"_ls].toArray().size()].toString()); 0138 m_recaptchaSiteKey = params["m.login.recaptcha"_ls]["public_key"_ls].toString(); 0139 Q_EMIT recaptchaSiteKeyChanged(); 0140 m_terms.clear(); 0141 for (const auto &term : params["m.login.terms"_ls]["policies"_ls].toObject().keys()) { 0142 QVariantMap termData; 0143 termData["title"_ls] = params["m.login.terms"_ls]["policies"_ls][term]["en"_ls]["name"_ls].toString(); 0144 termData["url"_ls] = params["m.login.terms"_ls]["policies"_ls][term]["en"_ls]["url"_ls].toString(); 0145 m_terms += termData; 0146 Q_EMIT termsChanged(); 0147 } 0148 }); 0149 } 0150 0151 QString Registration::homeserver() const 0152 { 0153 return m_homeserver; 0154 } 0155 0156 void Registration::setHomeserver(const QString &url) 0157 { 0158 m_homeserver = url; 0159 Q_EMIT homeserverChanged(); 0160 } 0161 0162 void Registration::testHomeserver() 0163 { 0164 if (m_homeserver.isEmpty()) { 0165 setStatus(NoServer); 0166 return; 0167 } 0168 setStatus(TestingHomeserver); 0169 if (m_connection) { 0170 delete m_connection; 0171 } 0172 0173 m_connection = new Connection(this); 0174 m_connection->resolveServer("@user:%1"_ls.arg(m_homeserver)); 0175 connectSingleShot(m_connection.data(), &Connection::loginFlowsChanged, this, [this]() { 0176 if (m_testServerJob) { 0177 delete m_testServerJob; 0178 } 0179 m_testServerJob = m_connection->callApi<NeoChatRegisterJob>("user"_ls, none, "user"_ls, QString(), QString(), QString(), false); 0180 connect(m_testServerJob.data(), &BaseJob::finished, this, [this]() { 0181 if (m_testServerJob->error() == BaseJob::StatusCode::ContentAccessError) { 0182 setStatus(ServerNoRegistration); 0183 return; 0184 } 0185 if (m_testServerJob->status().code != 106) { 0186 setStatus(InvalidServer); 0187 return; 0188 } 0189 if (!m_username.isEmpty()) { 0190 setStatus(TestingUsername); 0191 testUsername(); 0192 } else { 0193 setStatus(NoUsername); 0194 } 0195 }); 0196 }); 0197 } 0198 0199 void Registration::setUsername(const QString &username) 0200 { 0201 m_username = username; 0202 Q_EMIT usernameChanged(); 0203 } 0204 0205 QString Registration::username() const 0206 { 0207 return m_username; 0208 } 0209 0210 void Registration::testUsername() 0211 { 0212 if (status() <= ServerNoRegistration) { 0213 return; 0214 } 0215 setStatus(TestingUsername); 0216 if (m_usernameJob) { 0217 m_usernameJob->abandon(); 0218 } 0219 if (m_username.isEmpty()) { 0220 setStatus(NoUsername); 0221 return; 0222 } 0223 0224 m_usernameJob = m_connection->callApi<CheckUsernameAvailabilityJob>(m_username); 0225 connect(m_usernameJob, &BaseJob::result, this, [this]() { 0226 setStatus(m_usernameJob->error() == BaseJob::StatusCode::Success && *m_usernameJob->available() ? Ready : UsernameTaken); 0227 }); 0228 } 0229 0230 QList<QVariantMap> Registration::terms() const 0231 { 0232 return m_terms; 0233 } 0234 0235 QString Registration::password() const 0236 { 0237 return m_password; 0238 } 0239 0240 void Registration::setPassword(const QString &password) 0241 { 0242 m_password = password; 0243 Q_EMIT passwordChanged(); 0244 } 0245 0246 NeoChatRegisterJob::NeoChatRegisterJob(const QString &kind, 0247 const Omittable<QJsonObject> &auth, 0248 const QString &username, 0249 const QString &password, 0250 const QString &deviceId, 0251 const QString &initialDeviceDisplayName, 0252 Omittable<bool> inhibitLogin) 0253 : BaseJob(HttpVerb::Post, "RegisterJob"_ls, QByteArrayLiteral("/_matrix/client/r0/register"), false) 0254 { 0255 QJsonObject _data; 0256 if (auth) { 0257 addParam<>(_data, "auth"_ls, auth); 0258 } 0259 addParam<>(_data, "username"_ls, username); 0260 addParam<IfNotEmpty>(_data, "password"_ls, password); 0261 addParam<IfNotEmpty>(_data, "device_id"_ls, deviceId); 0262 addParam<IfNotEmpty>(_data, "initial_device_display_name"_ls, initialDeviceDisplayName); 0263 addParam<IfNotEmpty>(_data, "inhibit_login"_ls, inhibitLogin); 0264 addParam<IfNotEmpty>(_data, "kind"_ls, kind); 0265 addParam<IfNotEmpty>(_data, "refresh_token"_ls, false); 0266 setRequestData(_data); 0267 } 0268 0269 QString Registration::email() const 0270 { 0271 return m_email; 0272 } 0273 0274 void Registration::setEmail(const QString &email) 0275 { 0276 m_email = email; 0277 Q_EMIT emailChanged(); 0278 } 0279 0280 QString Registration::nextStep() const 0281 { 0282 return m_nextStep; 0283 } 0284 0285 void Registration::setNextStep(const QString &nextStep) 0286 { 0287 m_nextStep = nextStep; 0288 Q_EMIT nextStepChanged(); 0289 } 0290 0291 Registration::Status Registration::status() const 0292 { 0293 return m_status; 0294 } 0295 0296 QString Registration::statusString() const 0297 { 0298 switch (m_status) { 0299 case NoServer: 0300 return i18n("No server."); 0301 case TestingHomeserver: 0302 return i18n("Checking Server availability."); 0303 case InvalidServer: 0304 return i18n("This is not a valid server."); 0305 case ServerNoRegistration: 0306 return i18n("Registration for this server is disabled."); 0307 case NoUsername: 0308 return i18n("No username."); 0309 case TestingUsername: 0310 return i18n("Checking username availability."); 0311 case UsernameTaken: 0312 return i18n("This username is not available."); 0313 case Ready: 0314 return i18n("Continue"); 0315 case Working: 0316 return i18n("Working"); 0317 } 0318 return {}; 0319 } 0320 0321 void Registration::setStatus(Status status) 0322 { 0323 m_status = status; 0324 Q_EMIT statusChanged(); 0325 } 0326 0327 void Registration::registerEmail() 0328 { 0329 m_emailSecret = QString::fromLatin1(QUuid::createUuid().toString().toLatin1().toBase64()); 0330 EmailValidationData data; 0331 data.email = m_email; 0332 data.clientSecret = m_emailSecret; 0333 data.sendAttempt = 0; 0334 0335 auto job = m_connection->callApi<RequestTokenToRegisterEmailJob>(data); 0336 connect(job, &BaseJob::finished, this, [this, job]() { 0337 m_sid = job->jsonData()["sid"_ls].toString(); 0338 }); 0339 } 0340 0341 #include "moc_registration.cpp"