diff --git a/CMakeLists.txt b/CMakeLists.txt index d7971d9..f8c6d02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ set(PRJ_LIBRARIES Boost::iostreams Boost::thread Boost::filesystem + Boost::outcome cryptopp::cryptopp ZLIB::ZLIB OpenSSL::SSL @@ -86,7 +87,7 @@ find_package(fmt CONFIG REQUIRED) find_package(doctest CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(httplib CONFIG REQUIRED) -find_package(Boost REQUIRED COMPONENTS system iostreams thread filesystem) +find_package(Boost REQUIRED COMPONENTS system iostreams thread filesystem outcome) find_package(cryptopp CONFIG REQUIRED) find_package(ZLIB REQUIRED) find_package(OpenSSL REQUIRED) diff --git a/src/Identity.cpp b/src/Identity.cpp index 480ac2d..88d7fb5 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -1,6 +1,7 @@ #include "Identity.h" #include "Http.h" #include +#include #include #include #include @@ -113,3 +114,95 @@ std::string Identity::login(const std::string& fields) { d.erase("public_key"); return d.dump(); } + +Result ident::login_cached() noexcept { + std::string private_key; + try { + std::ifstream key_file(ident::KEYFILE); + if (key_file.is_open()) { + auto size = fs::file_size(ident::KEYFILE); + key_file.read(&private_key[0], static_cast(size)); + key_file.close(); + } + } catch (const std::exception& e) { + return fmt::format("Failed to read cached key: {}", e.what()); + } + + std::string pk_json {}; + + try { + pk_json = nlohmann::json { + { "pk", private_key } + }.dump(); + } catch (const std::exception& e) { + spdlog::error("Private key had invalid format, please log in again."); + return std::string("Invalid login saved, please log in again."); + } + + return detail::login(pk_json); +} + +bool ident::is_login_cached() noexcept { + return std::filesystem::exists(KEYFILE) && std::filesystem::is_regular_file(KEYFILE); +} + +Result ident::login(const std::string& username_or_email, const std::string& password) { + std::string login_json {}; + + try { + login_json = nlohmann::json { + { "username", username_or_email }, + { "password", password }, + } + .dump(); + } catch (const std::exception& e) { + spdlog::error("Username or password has invalid format, please try again."); + return std::string("Username or password contain illegal characters, please try again."); + } + + return detail::login(login_json); +} + +void ident::detail::cache_key(const std::string& private_key) noexcept { + try { + std::ofstream key_file(ident::KEYFILE, std::ios::trunc); + key_file << private_key; + } catch (const std::exception& e) { + spdlog::warn("Failed to cache key - login will not be remembered: {}", e.what()); + } +} +Result ident::detail::login(const std::string& json_params, bool remember) noexcept { + auto result = HTTP::Post("https://auth.beammp.com/userlogin", json_params); + + nlohmann::json json = nlohmann::json::parse(result, nullptr, false); + + if (result == "-1" || result.at(0) != '{' || json.is_discarded()) { + spdlog::error("auth.beammp.com failed to respond with valid user details"); + spdlog::trace("auth.beammp.com/userlogin responded with: {}", result); + return std::string("Invalid answer from auth server. Please check your internet connection and see if you can reach https://beammp.com."); + } + + try { + if (json["success"].get()) { + spdlog::info("{}", json["message"].get()); + ident::Identity id { + .PublicKey = json["public_key"].get(), + .PrivateKey = json["private_key"].get(), + .Role = json["role"].get(), + .Username = json["username"].get(), + .Message = json["message"].get(), + }; + if (remember) { + cache_key(id.PrivateKey); + } + return id; + } else { + spdlog::info("Auto-Authentication unsuccessful please re-login!"); + return std::string("Failed to auto-login with saved details, please login again."); + } + } catch (const std::exception& e) { + spdlog::error("Incomplete or invalid answer from auth servers. Please try logging in again."); + spdlog::trace("auth.beammp.com/userlogin responded with incomplete data: {}", result); + return std::string("Failed to auto-login with saved details, because the auth server responded with invalid details."); + } +} diff --git a/src/Identity.h b/src/Identity.h index fb7d92c..aed6e27 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -1,19 +1,34 @@ #pragma once - +#include #include +#include "Result.h" + +namespace ident { + +constexpr const char* KEYFILE = "key"; + struct Identity { - Identity(); - - void check_local_key(); - - bool LoginAuth { false }; std::string PublicKey {}; std::string PrivateKey {}; std::string Role {}; std::string Username {}; - - std::string login(const std::string& fields); - -private: - void update_key(const char* newKey); + std::string Message {}; }; + +/// Whether a login is cached / remembered +bool is_login_cached() noexcept; + +Result login_cached() noexcept; + +Result login(const std::string& username_or_email, const std::string& password); + +namespace detail { + + Result login(const std::string& json_params, bool remember) noexcept; + + void cache_key(const std::string& private_key) noexcept; + +} + +} + diff --git a/vcpkg.json b/vcpkg.json index 38dd166..f26b9f7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -11,6 +11,7 @@ "boost-iostreams", "boost-process", "boost-thread", + "boost-outcome", "nlohmann-json", "cryptopp", "zstd"