diff --git a/src/ServerProcess.cpp b/src/ServerProcess.cpp index 923776d..06bdd30 100644 --- a/src/ServerProcess.cpp +++ b/src/ServerProcess.cpp @@ -4,12 +4,14 @@ #include #include #include +#include #include #include namespace { constexpr int kDefaultAdminPort = 7779; constexpr int kAdminTimeoutMs = 3000; +constexpr auto kAdminTokenFileName = ".admin-token"; } ServerProcess::ServerProcess(QObject *parent) @@ -86,8 +88,33 @@ void ServerProcess::stop() { } QJsonObject ServerProcess::sendAdminCommand(const QJsonObject &request) { + if (serverDir.isEmpty()) { + return QJsonObject{ + {QStringLiteral("ok"), false}, + {QStringLiteral("error"), QStringLiteral("Server directory is unavailable for admin authentication")} + }; + } + + QFile tokenFile(QDir(serverDir).absoluteFilePath(QString::fromLatin1(kAdminTokenFileName))); + if (!tokenFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QJsonObject{ + {QStringLiteral("ok"), false}, + {QStringLiteral("error"), QStringLiteral("Admin authentication token is not available yet")} + }; + } + const QByteArray token = tokenFile.readAll().trimmed(); + tokenFile.close(); + if (token.isEmpty()) { + return QJsonObject{ + {QStringLiteral("ok"), false}, + {QStringLiteral("error"), QStringLiteral("Admin authentication token is empty")} + }; + } + + QJsonObject authenticatedRequest = request; + authenticatedRequest.insert(QStringLiteral("adminToken"), QString::fromUtf8(token)); + QTcpSocket socket; - // Force IPv4 loopback — matches server admin bind on 127.0.0.1. socket.connectToHost(QStringLiteral("127.0.0.1"), static_cast(m_adminPort)); if (!socket.waitForConnected(kAdminTimeoutMs)) { return QJsonObject{ @@ -99,7 +126,7 @@ QJsonObject ServerProcess::sendAdminCommand(const QJsonObject &request) { }; } - const QByteArray payload = QJsonDocument(request).toJson(QJsonDocument::Compact) + '\n'; + const QByteArray payload = QJsonDocument(authenticatedRequest).toJson(QJsonDocument::Compact) + '\n'; if (socket.write(payload) < 0 || !socket.waitForBytesWritten(kAdminTimeoutMs)) { return QJsonObject{ {QStringLiteral("ok"), false}, @@ -140,7 +167,6 @@ void ServerProcess::fetchStats() { emit statsUpdated(statsResponse.value(QStringLiteral("data")).toObject()); } else { ++adminFailCount; - // Avoid spamming the log every second while the admin port is still starting. if (adminFailCount == 3 || adminFailCount == 10 || (adminFailCount % 30) == 0) { emit logMessage(QStringLiteral("[ADMIN] %1") .arg(statsResponse.value(QStringLiteral("error")) @@ -315,8 +341,6 @@ QString ServerProcess::findServerDirectory() { } } - // Fallback: accept server dirs without admin_server.py so older layouts still launch, - // but prefer ones that include the admin channel when present. dir = QDir(QCoreApplication::applicationDirPath()); for (int i = 0; i < 8; ++i) { const QString candidate = dir.absoluteFilePath(QStringLiteral("server"));