diff --git a/Tubio/HttpServer.cpp b/Tubio/HttpServer.cpp index 6a8f807..9311806 100644 --- a/Tubio/HttpServer.cpp +++ b/Tubio/HttpServer.cpp @@ -82,6 +82,7 @@ void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p) http_message* hpm = (http_message*)p; std::string requestedUri = FixUnterminatedString(hpm->uri.p, hpm->uri.len); + // Get clients ip address std::string peer_addr; { char buf[32]; @@ -89,13 +90,7 @@ void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p) peer_addr = buf; } - if ((XGConfig::general.onlyAllowLocalhost) && (peer_addr != "127.0.0.1")) - { - Json j; - j.CloneFrom(RestResponseTemplates::GetByCode(UNAUTHORIZED, "Only localhost allowed!")); - ServeStringToConnection(pNc, j.Render(), UNAUTHORIZED); - } - else + if (IsConnectionAllowed(peer_addr)) { try { @@ -128,6 +123,12 @@ void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p) break; } + else // Client is not allowed, serve error json + { + Json j; + j.CloneFrom(RestResponseTemplates::GetByCode(UNAUTHORIZED, "Only localhost allowed!")); + ServeStringToConnection(pNc, j.Render(), UNAUTHORIZED); + } } return; @@ -199,6 +200,36 @@ void HttpServer::ServeDownloadeableResource(mg_connection* pNc, int ev, void* p, return; } +bool HttpServer::IsConnectionAllowed(std::string peer_address) +{ + // Localhost is always allowed! + if (peer_address == "127.0.0.1") return true; + + // Peer is not localhost, but only localhost is allowed! + else if (XGConfig::access.only_allow_localhost) return false; + + // Let's check if the whitelist is active + else if (XGConfig::access.enable_whitelist) + { + // It is. Let's check if our peer is whitelisted + for (std::size_t i = 0; i < XGConfig::access.whitelist.size(); i++) + { + // Whitelist is enabled, and peer is whitelisted + if (XGConfig::access.whitelist[i] == peer_address) return true; + } + + // Whitelist is enabled, but peer is NOT whitelisted + return false; + } + else // Whitelist is NOT enabled and only_allow_localhost is FALSE! + { + return true; + } + + // Should never come to this point + return false; +} + void HttpServer::OnExit() { log->cout << "Shutting down http-server..."; diff --git a/Tubio/HttpServer.h b/Tubio/HttpServer.h index 1333e29..0a66d75 100644 --- a/Tubio/HttpServer.h +++ b/Tubio/HttpServer.h @@ -29,6 +29,8 @@ namespace Rest static void ServeStringToConnection(struct mg_connection* c, std::string str, int httpStatusCode = 200); static std::string FixUnterminatedString(const char* cstr, const std::size_t len); + static bool IsConnectionAllowed(std::string peer_address); + struct mg_mgr* pMgr; struct mg_connection* pNc; diff --git a/Tubio/RestQueryHandler.cpp b/Tubio/RestQueryHandler.cpp index 6972586..4413bf8 100644 --- a/Tubio/RestQueryHandler.cpp +++ b/Tubio/RestQueryHandler.cpp @@ -42,6 +42,7 @@ bool RestQueryHandler::ProcessQuery(const std::string clientAdress, const Json& else if (requestName == "fetch_alltime_logs") return FetchAlltimeLogs(requestBody, responseBody, responseCode); else if (requestName == "update_dep_youtubedl") return UpdateYoutubeDL(requestBody, responseBody, responseCode); else if (requestName == "remove_download_entry") return RemoveDownloadEntry(requestBody, responseBody, responseCode); + else if (requestName == "update_config") return UpdateConfig(requestBody, responseBody, responseCode); @@ -437,6 +438,44 @@ bool RestQueryHandler::RemoveDownloadEntry(const JsonBlock& request, JsonBlock& return true; } +bool RestQueryHandler::UpdateConfig(const JsonBlock& request, JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode) +{ + if (ValidateField("config", JDType::JSON, request, responseBody)) + { + bool prevStateConsole = XGConfig::general.show_console; + XGConfig::LoadFromJson(request.Get("config").AsJson); + + // Update console, if necessary + if (XGConfig::general.show_console != prevStateConsole) + { + if (XGConfig::general.show_console) ConsoleManager::ShowConsole(); + else ConsoleManager::HideConsole(); + } + + log->cout << "Updated config values..."; + log->Flush(); + XGConfig::Save(); + + responseBody.Set("message") = "Updated no settings"; + } + else + { + responseBody.Set("message") = "Updated no settings"; + } + + responseCode = OK; + responseBody.CloneFrom(RestResponseTemplates::GetByCode(OK)); + responseBody.Set("settings") = XGConfig::GetSavefileBuffer(); + return true; +} + + + + + + + + bool RestQueryHandler::ValidateField(const std::string name, const JasonPP::JDType type, const JasonPP::Json& checkThat, JasonPP::JsonBlock& putErrorResponseHere) diff --git a/Tubio/RestQueryHandler.h b/Tubio/RestQueryHandler.h index 5241daf..0331b44 100644 --- a/Tubio/RestQueryHandler.h +++ b/Tubio/RestQueryHandler.h @@ -34,6 +34,7 @@ namespace Rest static bool GetDiskUsage(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode); static bool UpdateYoutubeDL(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode); static bool RemoveDownloadEntry(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode); + static bool UpdateConfig(const JasonPP::JsonBlock& request, JasonPP::JsonBlock& responseBody, HTTP_STATUS_CODE& responseCode); static bool ValidateField(const std::string name, const JasonPP::JDType type, const JasonPP::Json& checkThat, JasonPP::JsonBlock& putErrorResponseHere); diff --git a/Tubio/XGConfig.cpp b/Tubio/XGConfig.cpp index 749772a..243f143 100644 --- a/Tubio/XGConfig.cpp +++ b/Tubio/XGConfig.cpp @@ -45,13 +45,20 @@ void XGConfig::InitializeDefaultValues() downloader.num_threads = 1; general.show_console = true; - general.onlyAllowLocalhost = true; + + access.only_allow_localhost = true; + access.enable_whitelist = true; + access.whitelist = std::vector(); + access.whitelist.push_back("127.0.0.1"); // Add localhost to whitelist return; } void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json) { + savefileBuffer.Clear(); + savefileBuffer.CloneFrom(json); + if (IsJsonFieldValid(json, "logging.logfile_json", JDType::STRING)) { logging.logfile_json = json.ShorthandGet("logging.logfile_json").AsString; @@ -103,9 +110,28 @@ void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json) { general.show_console = json.ShorthandGet("general.show_console").AsBool; } - if (IsJsonFieldValid(json, "general.onlyAllowLocalhost", JDType::BOOL)) + + + + if (IsJsonFieldValid(json, "access.only_allow_localhost", JDType::BOOL)) { - general.onlyAllowLocalhost = json.ShorthandGet("general.onlyAllowLocalhost").AsBool; + access.only_allow_localhost = json.ShorthandGet("access.only_allow_localhost").AsBool; + } + if (IsJsonFieldValid(json, "access.enable_whitelist", JDType::BOOL)) + { + access.enable_whitelist = json.ShorthandGet("access.enable_whitelist").AsBool; + } + if (IsJsonFieldValid(json, "access.whitelist", JDType::ARRAY)) + { + const JsonArray& cachedArr = json.ShorthandGet("access.whitelist").AsArray; + access.whitelist.clear(); + for (std::size_t i = 0; i < cachedArr.Size(); i++) + { + if (cachedArr[i].GetDataType() == JDType::STRING) + { + access.whitelist.push_back(cachedArr[i].AsString); + } + } } return; @@ -113,10 +139,13 @@ void XGConfig::LoadFromJson(const JasonPP::JsonBlock& json) JsonBlock XGConfig::CreateJson() { + JsonArray jsnArrWhitelist; + jsnArrWhitelist.Add(access.whitelist); + return JsonBlock({ Ele("httpServer", JsonBlock({ Ele("port", httpServer.port), - Ele("pollingRate", httpServer.polling_rate), + Ele("polling_rate", httpServer.polling_rate), Ele("rootdir", httpServer.rootdir) })), Ele("logging", JsonBlock({ @@ -131,7 +160,11 @@ JsonBlock XGConfig::CreateJson() })), Ele("general", JsonBlock({ Ele("show_console", general.show_console), - Ele("onlyAllowLocalhost", general.onlyAllowLocalhost) + })), + Ele("access", JsonBlock({ + Ele("only_allow_localhost", access.only_allow_localhost), + Ele("enable_whitelist", access.enable_whitelist), + Ele("whitelist", jsnArrWhitelist), })) }); } @@ -210,6 +243,8 @@ void XGConfig::Save() bool XGConfig::SaveToFile(std::string filename) { Json cfgStruct(CreateJson()); + savefileBuffer.Clear(); + savefileBuffer.CloneFrom(cfgStruct.AsJson); return FileSystem::WriteFile(filename, cfgStruct.Render()); } @@ -245,5 +280,7 @@ XGConfig::HttpServer XGConfig::httpServer; XGConfig::Logging XGConfig::logging; XGConfig::Downloader XGConfig::downloader; XGConfig::General XGConfig::general; +XGConfig::Access XGConfig::access; +JasonPP::JsonBlock XGConfig::savefileBuffer; ::Logging::Logger* XGConfig::log; diff --git a/Tubio/XGConfig.h b/Tubio/XGConfig.h index 5a84b13..36a88f2 100644 --- a/Tubio/XGConfig.h +++ b/Tubio/XGConfig.h @@ -1,4 +1,5 @@ #pragma once +#include #include "Filesystem.h" #include "external_dependencies/leonetienne/JasonPP/JasonPP.hpp" #include "Logger.h" @@ -30,27 +31,37 @@ public: struct General { bool show_console; - bool onlyAllowLocalhost; + }; + struct Access + { + bool only_allow_localhost; + bool enable_whitelist; + std::vector whitelist; }; static void PreInit(); static void Save(); static void PostExit(); + static void LoadFromJson(const JasonPP::JsonBlock& json); + static XGConfig::HttpServer httpServer; static XGConfig::Logging logging; static XGConfig::Downloader downloader; static XGConfig::General general; + static XGConfig::Access access; + + static const JasonPP::JsonBlock& GetSavefileBuffer() { return savefileBuffer; } static ::Logging::Logger* log; private: static bool SaveToFile(std::string filename); + static JasonPP::JsonBlock savefileBuffer; static bool IsJsonFieldValid(const JasonPP::JsonBlock& json, const std::string key, const JasonPP::JDType type); static void InitializeDefaultValues(); static JasonPP::JsonBlock CreateJson(); - static void LoadFromJson(const JasonPP::JsonBlock& json); static void Load(); }; diff --git a/Tubio_rest.postman_collection.json b/Tubio_rest.postman_collection.json index 4549221..5f46249 100644 --- a/Tubio_rest.postman_collection.json +++ b/Tubio_rest.postman_collection.json @@ -39,7 +39,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://youtu.be/OF-thWTJcu0\",\r\n \"mode\": \"video\"\r\n}", + "raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://www.youtube.com/watch?v=BHiWygziyso\",\r\n \"mode\": \"video\"\r\n}", "options": { "raw": { "language": "json" @@ -66,7 +66,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://youtu.be/wgfNsek8xkc\",\r\n \"mode\": \"audio\"\r\n}", + "raw": "{\r\n \"request\": \"queue_download\",\r\n \"video_url\": \"https://youtu.be/m9HT74QccbQ\",\r\n \"mode\": \"audio\"\r\n}", "options": { "raw": { "language": "json" @@ -93,7 +93,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"request\": \"fetch_session_cache\",\r\n \"max-num\": -1\r\n}\r\n", + "raw": "{\r\n \"request\": \"fetch_session_cache\",\r\n \"max_age\": -1\r\n}\r\n", "options": { "raw": { "language": "json" @@ -426,7 +426,61 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"request\": \"remove_download_entry\",\r\n \"id\": \"1KnnPV\"\r\n}", + "raw": "{\r\n \"request\": \"remove_download_entry\",\r\n \"id\": \"1KnnMe\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:6969/api", + "host": [ + "localhost" + ], + "port": "6969", + "path": [ + "api" + ] + } + }, + "response": [] + }, + { + "name": "UpdateConfig", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"request\": \"update_config\",\r\n \"config\": {\r\n \"access\": {\r\n \"enable_whitelist\": false,\r\n \"only_allow_localhost\": false,\r\n \"whitelist\": [\r\n \"127.0.0.1\"\r\n ]\r\n },\r\n \"general\": {\r\n \"show_console\": true\r\n }\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:6969/api", + "host": [ + "localhost" + ], + "port": "6969", + "path": [ + "api" + ] + } + }, + "response": [] + }, + { + "name": "GetConfig", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"request\": \"update_config\"\r\n}", "options": { "raw": { "language": "json" diff --git a/tubio-frontend-nuxt-app/pages/settings.vue b/tubio-frontend-nuxt-app/pages/settings.vue index 911d1f6..701e03e 100644 --- a/tubio-frontend-nuxt-app/pages/settings.vue +++ b/tubio-frontend-nuxt-app/pages/settings.vue @@ -14,11 +14,6 @@ -
-

Limit speed

- -
-

Max speed

@@ -29,6 +24,11 @@
+
+

Enable whitelist

+ +
+
diff --git a/tubio-frontend-nuxt-app/store/settings.js b/tubio-frontend-nuxt-app/store/settings.js new file mode 100644 index 0000000..e69de29