RelayClient: fix syntax error in savedServers(); strip trailing slash on relay URLs

This commit is contained in:
Zac Gaetano 2026-05-07 00:15:56 -04:00
parent 9bcef4c0a9
commit f270350ae6

View file

@ -14,23 +14,22 @@
RelayClient::RelayClient(QObject *parent) RelayClient::RelayClient(QObject *parent)
: QObject(parent) : QObject(parent)
, m_nam(new QNetworkAccessManager(this)) , m_nam(new QNetworkAccessManager(this))
, m_settings(new QSettings(QStringLiteral("WildDragon"), QStringLiteral("DragonMoonlight"), this)) , m_settings(new QSettings(QStringLiteral("WildDragon"),
QStringLiteral("DragonMoonlight"), this))
{ {
} }
RelayClient::~RelayClient() = default; RelayClient::~RelayClient() = default;
// ─── URL helpers ──────────────────────────────────────────────────────────────
QUrl RelayClient::apiUrl(const QString &path) const QUrl RelayClient::apiUrl(const QString &path) const
{ {
QUrl u = m_relayUrl; QUrl u = m_relayUrl;
u.setPath(u.path() + path); QString base = u.path();
while (base.endsWith(QLatin1Char('/'))) base.chop(1);
u.setPath(base + path);
return u; return u;
} }
// ─── Auth-header injection ────────────────────────────────────────────────
QNetworkReply *RelayClient::authedGet(const QString &path) QNetworkReply *RelayClient::authedGet(const QString &path)
{ {
QNetworkRequest req(apiUrl(path)); QNetworkRequest req(apiUrl(path));
@ -53,8 +52,6 @@ QNetworkReply *RelayClient::authedDelete(const QString &path)
return m_nam->deleteResource(req); return m_nam->deleteResource(req);
} }
// ─── login() ─────────────────────────────────────────────────────────────────
void RelayClient::login(const QUrl &relayUrl, const QString &username, const QString &password) void RelayClient::login(const QUrl &relayUrl, const QString &username, const QString &password)
{ {
m_relayUrl = relayUrl; m_relayUrl = relayUrl;
@ -62,7 +59,8 @@ void RelayClient::login(const QUrl &relayUrl, const QString &username, const QSt
m_jwt.clear(); m_jwt.clear();
QNetworkRequest req(apiUrl(QStringLiteral("/api/auth/login"))); QNetworkRequest req(apiUrl(QStringLiteral("/api/auth/login")));
req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); req.setHeader(QNetworkRequest::ContentTypeHeader,
QByteArrayLiteral("application/json"));
QJsonObject body; QJsonObject body;
body[QStringLiteral("username")] = username; body[QStringLiteral("username")] = username;
@ -87,7 +85,8 @@ void RelayClient::onLoginReply(QNetworkReply *reply, const QString &username,
const QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); const QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object();
const QString token = json[QStringLiteral("token")].toString(); const QString token = json[QStringLiteral("token")].toString();
if (token.isEmpty()) { if (token.isEmpty()) {
const QString err = json[QStringLiteral("error")].toString(QStringLiteral("Unknown error")); const QString err = json[QStringLiteral("error")]
.toString(QStringLiteral("Unknown error"));
emit loginFailed(err); emit loginFailed(err);
return; return;
} }
@ -98,11 +97,8 @@ void RelayClient::onLoginReply(QNetworkReply *reply, const QString &username,
emit loginSucceeded(); emit loginSucceeded();
} }
// ─── provisionVPN() ───────────────────────────────────────────────────────────
void RelayClient::provisionVPN(const QString &deviceName) void RelayClient::provisionVPN(const QString &deviceName)
{ {
// Use the machine's host name if the caller didn't provide one.
const QString device = deviceName.isEmpty() const QString device = deviceName.isEmpty()
? QSysInfo::machineHostName() ? QSysInfo::machineHostName()
: deviceName; : deviceName;
@ -128,14 +124,14 @@ void RelayClient::onProvisionReply(QNetworkReply *reply)
const QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object(); const QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object();
const QString conf = json[QStringLiteral("conf")].toString(); const QString conf = json[QStringLiteral("conf")].toString();
if (conf.isEmpty()) { if (conf.isEmpty()) {
const QString err = json[QStringLiteral("error")].toString(QStringLiteral("No conf in response")); const QString err = json[QStringLiteral("error")]
.toString(QStringLiteral("No conf in response"));
emit vpnProvisionFailed(err); emit vpnProvisionFailed(err);
return; return;
} }
m_vpnPeerId = json[QStringLiteral("id")].toInt(-1); m_vpnPeerId = json[QStringLiteral("id")].toInt(-1);
// Persist peer ID so we can revoke it even after a restart.
m_settings->setValue(QStringLiteral("vpn/peer_id"), m_vpnPeerId); m_settings->setValue(QStringLiteral("vpn/peer_id"), m_vpnPeerId);
m_settings->setValue(QStringLiteral("vpn/relay_url"), m_relayUrl.toString()); m_settings->setValue(QStringLiteral("vpn/relay_url"), m_relayUrl.toString());
@ -144,20 +140,18 @@ void RelayClient::onProvisionReply(QNetworkReply *reply)
emit vpnProvisioned(vpnConf); emit vpnProvisioned(vpnConf);
} }
// ─── revokeVPN() ─────────────────────────────────────────────────────────────
void RelayClient::revokeVPN() void RelayClient::revokeVPN()
{ {
if (m_vpnPeerId < 0) { if (m_vpnPeerId < 0) {
// Try to load from settings (post-restart cleanup).
m_vpnPeerId = m_settings->value(QStringLiteral("vpn/peer_id"), -1).toInt(); m_vpnPeerId = m_settings->value(QStringLiteral("vpn/peer_id"), -1).toInt();
} }
if (m_vpnPeerId < 0) { if (m_vpnPeerId < 0) {
emit vpnRevoked(); // nothing to do emit vpnRevoked();
return; return;
} }
const QString path = QStringLiteral("/api/vpn/peer/") + QString::number(m_vpnPeerId); const QString path = QStringLiteral("/api/vpn/peer/")
+ QString::number(m_vpnPeerId);
QNetworkReply *reply = authedDelete(path); QNetworkReply *reply = authedDelete(path);
connect(reply, &QNetworkReply::finished, this, [this, reply]() { connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater(); reply->deleteLater();
@ -175,8 +169,6 @@ void RelayClient::onRevokeReply(QNetworkReply *reply)
emit vpnRevoked(); emit vpnRevoked();
} }
// ─── fetchHosts() ─────────────────────────────────────────────────────────────
void RelayClient::fetchHosts() void RelayClient::fetchHosts()
{ {
QNetworkReply *reply = authedGet(QStringLiteral("/api/hosts")); QNetworkReply *reply = authedGet(QStringLiteral("/api/hosts"));
@ -202,7 +194,6 @@ void RelayClient::onHostsReply(QNetworkReply *reply)
h.ip = obj[QStringLiteral("ip")].toString(); h.ip = obj[QStringLiteral("ip")].toString();
h.port = obj[QStringLiteral("port")].toInt(47984); h.port = obj[QStringLiteral("port")].toInt(47984);
// Parse displays array if present
const QJsonArray displaysArr = obj[QStringLiteral("displays")].toArray(); const QJsonArray displaysArr = obj[QStringLiteral("displays")].toArray();
for (const QJsonValue &dv : displaysArr) { for (const QJsonValue &dv : displaysArr) {
const QJsonObject displayObj = dv.toObject(); const QJsonObject displayObj = dv.toObject();
@ -216,7 +207,8 @@ void RelayClient::onHostsReply(QNetworkReply *reply)
} }
if (h.displays.isEmpty()) { if (h.displays.isEmpty()) {
qWarning() << "Host" << h.name << "has no display data in relay response"; qDebug() << "Host" << h.name
<< "has no display data in relay response (older Artemis?)";
} }
if (!h.ip.isEmpty()) if (!h.ip.isEmpty())
@ -225,21 +217,15 @@ void RelayClient::onHostsReply(QNetworkReply *reply)
emit hostsReady(hosts); emit hostsReady(hosts);
} }
// ─── Persistence ──────────────────────────────────────────────────────────────
void RelayClient::persistJwt() void RelayClient::persistJwt()
{ {
// NOTE: QSettings on macOS stores in ~/Library/Preferences.
// The JWT has a limited lifetime — don't rely on it across sessions;
// the app will re-authenticate on next launch using saved credentials.
m_settings->setValue(QStringLiteral("auth/relay_url"), m_relayUrl.toString()); m_settings->setValue(QStringLiteral("auth/relay_url"), m_relayUrl.toString());
m_settings->setValue(QStringLiteral("auth/username"), m_username); m_settings->setValue(QStringLiteral("auth/username"), m_username);
// We do NOT persist the JWT itself or the password.
} }
void RelayClient::loadJwt() void RelayClient::loadJwt()
{ {
// Intentionally empty re-auth on each launch for security. // Intentionally empty - we re-auth on each launch for security.
} }
void RelayClient::saveServer(const QString &label, const QUrl &url, void RelayClient::saveServer(const QString &label, const QUrl &url,
@ -248,8 +234,7 @@ void RelayClient::saveServer(const QString &label, const QUrl &url,
m_settings->beginGroup(QStringLiteral("servers/") + label); m_settings->beginGroup(QStringLiteral("servers/") + label);
m_settings->setValue(QStringLiteral("url"), url.toString()); m_settings->setValue(QStringLiteral("url"), url.toString());
m_settings->setValue(QStringLiteral("username"), username); m_settings->setValue(QStringLiteral("username"), username);
// password stored in the system keychain would be better; plain QSettings // TODO: replace with QKeychain so the password isn't stored in plaintext.
// as a baseline — replace with QKeychain in production.
m_settings->setValue(QStringLiteral("password"), password); m_settings->setValue(QStringLiteral("password"), password);
m_settings->endGroup(); m_settings->endGroup();
} }
@ -260,13 +245,13 @@ QList<RelayClient::SavedServer> RelayClient::savedServers() const
const QStringList groups = m_settings->childGroups(); const QStringList groups = m_settings->childGroups();
for (const QString &g : groups) { for (const QString &g : groups) {
if (!g.startsWith(QStringLiteral("servers/"))) continue; if (!g.startsWith(QStringLiteral("servers/"))) continue;
const QString label = g.mid(8); const QString label = g.mid(8); // length of "servers/"
m_settings->beginGroup(g); m_settings->beginGroup(g);
SavedServer s; SavedServer s;
s.label = label; s.label = label;
s.url = QUrl(m_settings->value(QStringLiteral("url")).toString()); s.url = QUrl(m_settings->value(QStringLiteral("url")).toString());
s.username = m_settings->value(QStringLiteral("username")).toString(); s.username = m_settings->value(QStringLiteral("username")).toString();
s.password = m_settings->value(QStringLiteral("password")).toString()); s.password = m_settings->value(QStringLiteral("password")).toString();
m_settings->endGroup(); m_settings->endGroup();
list << s; list << s;
} }