artemis/src/wg/relayreg.cpp

187 lines
6.9 KiB
C++

// src/wg/relayreg.cpp — DragonRelay HTTP registration client for Artemis.
//
// Depends on: libcurl, nlohmann/json (header-only, shipped with Sunshine)
//
// Build: add relayreg.cpp to the CMake target and link curl.
#include "relayreg.h"
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <string>
namespace wg {
using json = nlohmann::json;
// ── libcurl write callback ────────────────────────────────────────────────────
static size_t curlWrite(void *data, size_t size, size_t nmemb, void *userp) {
size_t total = size * nmemb;
static_cast<std::string *>(userp)->append(static_cast<char *>(data), total);
return total;
}
// ── RelayReg ──────────────────────────────────────────────────────────────────
RelayReg::RelayReg() {
curl_global_init(CURL_GLOBAL_DEFAULT);
}
RelayReg::~RelayReg() {
curl_global_cleanup();
}
// ── HTTP helper ───────────────────────────────────────────────────────────────
int RelayReg::request(const std::string &method,
const std::string &path,
const std::string &body,
std::string &responseOut) {
responseOut.clear();
CURL *curl = curl_easy_init();
if (!curl) return -1;
std::string url = m_base + path;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWrite);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseOut);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
// Headers
struct curl_slist *hdrs = nullptr;
hdrs = curl_slist_append(hdrs, "Content-Type: application/json");
if (!m_jwt.empty()) {
std::string auth = "Authorization: Bearer " + m_jwt;
hdrs = curl_slist_append(hdrs, auth.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
// Method + body
if (method == "POST" || method == "PUT") {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)body.size());
} else if (method == "DELETE") {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
}
// GET is default
CURLcode res = curl_easy_perform(curl);
long httpCode = 0;
if (res == CURLE_OK)
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_slist_free_all(hdrs);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
if (m_log) m_log(std::string("curl error: ") + curl_easy_strerror(res));
return -1;
}
return (int)httpCode;
}
// ── login ─────────────────────────────────────────────────────────────────────
bool RelayReg::login(const std::string &username, const std::string &password,
std::string &errOut) {
json body = { {"username", username}, {"password", password} };
std::string resp;
int code = request("POST", "/api/auth/login", body.dump(), resp);
if (code != 200) {
errOut = "login failed (HTTP " + std::to_string(code) + "): " + resp;
return false;
}
try {
auto j = json::parse(resp);
m_jwt = j.at("token").get<std::string>();
if (m_log) m_log("Logged in to DragonRelay");
return true;
} catch (const std::exception &e) {
errOut = std::string("login JSON parse: ") + e.what();
return false;
}
}
void RelayReg::logout() {
m_jwt.clear();
}
// ── provisionVPN ──────────────────────────────────────────────────────────────
bool RelayReg::provisionVPN(const std::string &deviceName,
VPNConf &out, std::string &errOut) {
json body = { {"device_name", deviceName} };
std::string resp;
int code = request("POST", "/api/vpn/peer", body.dump(), resp);
if (code != 200 && code != 201) {
errOut = "provisionVPN failed (HTTP " + std::to_string(code) + "): " + resp;
return false;
}
try {
auto j = json::parse(resp);
out.id = j.at("id").get<std::string>();
out.name = j.at("name").get<std::string>();
out.publicKey = j.at("public_key").get<std::string>();
out.allowedIP = j.at("allowed_ip").get<std::string>();
out.conf = j.at("conf").get<std::string>();
if (m_log) m_log("VPN peer provisioned: " + out.name + " @ " + out.allowedIP);
return true;
} catch (const std::exception &e) {
errOut = std::string("provisionVPN JSON: ") + e.what();
return false;
}
}
bool RelayReg::deleteVPNPeer(const std::string &peerId, std::string &errOut) {
std::string resp;
int code = request("DELETE", "/api/vpn/peer/" + peerId, "", resp);
if (code != 200 && code != 204) {
errOut = "deleteVPNPeer failed (HTTP " + std::to_string(code) + ")";
return false;
}
return true;
}
// ── registerHost ──────────────────────────────────────────────────────────────
bool RelayReg::registerHost(const std::string &name, const std::string &wgIP,
int port, std::string &errOut) {
json body = { {"name", name}, {"wg_ip", wgIP}, {"port", port} };
std::string resp;
int code = request("POST", "/api/host/register", body.dump(), resp);
if (code != 200 && code != 201) {
errOut = "registerHost failed (HTTP " + std::to_string(code) + "): " + resp;
return false;
}
if (m_log) m_log("Registered host '" + name + "' at " + wgIP + ":" + std::to_string(port));
return true;
}
bool RelayReg::heartbeat(std::string &errOut) {
std::string resp;
int code = request("PUT", "/api/host/heartbeat", "{}", resp);
if (code != 200 && code != 204) {
errOut = "heartbeat failed (HTTP " + std::to_string(code) + ")";
return false;
}
return true;
}
bool RelayReg::unregisterHost(std::string &errOut) {
std::string resp;
int code = request("DELETE", "/api/host/unregister", "", resp);
if (code != 200 && code != 204) {
errOut = "unregisterHost failed (HTTP " + std::to_string(code) + ")";
return false;
}
if (m_log) m_log("Unregistered from DragonRelay");
return true;
}
} // namespace wg