Implement authentication

This commit is contained in:
2025-06-14 01:05:44 +04:00
parent 912bf3c8f0
commit 16c7e94345
20 changed files with 401 additions and 25 deletions

View File

@@ -27,14 +27,35 @@ add_subdirectory(third_party/spdlog)
add_executable(${PROJECT_NAME}
src/main.cpp
src/dependency_injection/DependencyContainer.h
src/utils/JsonConversion.cpp
src/utils/JsonConversion.h
src/controllers/AuthController.cpp
src/controllers/AuthController.h
src/models/Entity.h
src/models/User.h
src/services/AuthService.cpp
src/services/AuthService.h
src/services/UserManager.cpp
src/services/UserManager.h
src/services/encryption/PasswordEncryptor.cpp
src/services/encryption/PasswordEncryptor.h
src/services/encryption/SHA256Hasher.cpp
src/services/encryption/SHA256Hasher.h
src/storages/IUserStorage.h
src/mock_storage_implementation/UserStorage.cpp
src/mock_storage_implementation/UserStorage.h
)
# Copy config dir to build dir
set(CONFIG_SOURCE_DIR ${CMAKE_SOURCE_DIR}/config)
file(COPY ${CONFIG_SOURCE_DIR} DESTINATION ${CMAKE_BINARY_DIR})
target_include_directories(
${PROJECT_NAME} PRIVATE
src/
)
target_link_libraries(
${PROJECT_NAME} PRIVATE
drogon

View File

@@ -1,21 +1,49 @@
#include "AuthController.h"
#include <spdlog/spdlog.h>
#include "dependency_injection/DependencyContainer.h"
#include "services/AuthService.h"
#include "utils/JsonConversion.h"
api::v1::Auth::Auth()
{
m_logger = DependencyContainer::Resolve<spdlog::logger>();
m_authService = DependencyContainer::Resolve<AuthService>();
m_logger->info("[api::v1::Auth::Auth] Registered Auth controller");
}
void api::v1::Auth::login(
const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& callback,
std::string&& username,
const std::string& password
const std::string& username,
std::string&& password
)
{
LOG_DEBUG << "User " << username << " login";
Json::Value ret;
ret["result"] = "ok";
ret["token"] = drogon::utils::getUuid();
auto resp = drogon::HttpResponse::newHttpJsonResponse(ret);
callback(resp);
m_logger->info("[api::v1::Auth::login] User `{}` tries to login", username);
std::string token{};
bool result = m_authService->authenticateUser(username, std::move(password), token);
if (result)
{
m_logger->info("[api::v1::Auth::login] User `{}` successfully logged in", username);
nlohmann::json json;
json["result"] = "ok";
auto resp = drogon::HttpResponse::newHttpJsonResponse(nlohmannToJsonValue(json));
resp->addHeader("Authorization", "Bearer " + token);
callback(resp);
}
else
{
m_logger->info("[api::v1::Auth::login] User `{}` failed to login", username);
nlohmann::json json;
json["result"] = "error";
json["message"] = "Invalid username or password";
auto resp = drogon::HttpResponse::newHttpJsonResponse(nlohmannToJsonValue(json));
resp->setStatusCode(drogon::k401Unauthorized);
callback(resp);
}
}

View File

@@ -1,5 +1,8 @@
#include <drogon/drogon.h>
class AuthService;
class spdlog::logger;
namespace api
{
namespace v1
@@ -16,11 +19,12 @@ public:
void login(const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& callback,
std::string&& username,
const std::string& password);
const std::string& username,
std::string&& password);
private:
std::shared_ptr<spdlog::logger> m_logger;
std::shared_ptr<AuthService> m_authService;
};
}

View File

@@ -1,11 +1,11 @@
#pragma once
#include <unordered_map>
#include <typeindex>
#include <memory>
#include <functional>
#include <stdexcept>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <typeindex>
#include <unordered_map>
enum class Lifetime
{
@@ -19,8 +19,6 @@ public:
template <typename Interface, typename Impl, Lifetime lifetime = Lifetime::Singleton>
static void Register()
{
std::lock_guard<std::mutex> lock(GetInstance().m_mutex);
auto factory = []() -> std::shared_ptr<void>
{
return std::make_shared<Impl>();
@@ -50,7 +48,6 @@ public:
template <typename Interface>
static void RegisterInstance(std::shared_ptr<Interface> instance) {
std::lock_guard<std::mutex> lock(GetInstance().m_mutex);
GetInstance().m_singletons[typeid(Interface)] = instance;
GetInstance().m_factories[typeid(Interface)] = []()
{
@@ -61,13 +58,12 @@ public:
template <typename Interface>
static std::shared_ptr<Interface> Resolve()
{
std::lock_guard<std::mutex> lock(GetInstance().m_mutex);
auto& instance = GetInstance();
auto fit = instance.m_factories.find(typeid(Interface));
if (fit == instance.m_factories.end())
{
throw std::runtime_error("No registered factory for type.");
throw std::runtime_error(std::string{ "No registered factory for type " } + typeid(Interface).name());
}
return std::static_pointer_cast<Interface>(fit->second());

View File

@@ -1,5 +1,4 @@
#include <iostream>
#include <filesystem>
#include <drogon/drogon.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
@@ -7,6 +6,11 @@
#include <spdlog/sinks/msvc_sink.h>
#include "dependency_injection/DependencyContainer.h"
#include "services/AuthService.h"
#include "services/UserManager.h"
#include "services/encryption/SHA256Hasher.h"
#include "mock_storage_implementation/UserStorage.h"
class LoggerSingleton
{
public:
@@ -62,11 +66,24 @@ int main()
{
registerLogger();
auto logger = DependencyContainer::Resolve<spdlog::logger>();
logger->info("Hello from log");
logger->info("Starting server application...");
drogon::app()
.loadConfigFile("./config/config.json")
.run();
// Register dependencies
DependencyContainer::Register<AuthService, AuthService>();
DependencyContainer::Register<UserManager, UserManager>();
DependencyContainer::Register<PasswordEncryptor, SHA256Hasher>();
DependencyContainer::Register<IUserStorage, MockUserStorage>();
try
{
drogon::app()
.loadConfigFile("./config/config.json")
.run();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,14 @@
#include "UserStorage.h"
std::optional<User> MockUserStorage::getUserByUsername(const std::string& username)
{
User ret;
ret.id = 42;
ret.username = "niggerfaggot";
ret.passwordHash = "ZPQFaYapAAMJ3MYdNcfrI4JFm4J8IEXg9LzrLKA63Q4=";
ret.firstName = "Nigger";
ret.lastName = "Faggot";
ret.role = "LIVER";
return ret;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "storages/IUserStorage.h"
class MockUserStorage : public IUserStorage
{
public:
virtual std::optional<User> getUserByUsername(const std::string& username) override;
};

11
src/models/Entity.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <nlohmann/json.hpp>
class Entity
{
public:
int id;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Entity, id);
};

16
src/models/User.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "Entity.h"
class User : public Entity
{
public:
std::string username;
std::string passwordHash;
std::string firstName;
std::string lastName;
std::string role;
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(User, Entity,
username, passwordHash, firstName, lastName, role);
};

View File

@@ -0,0 +1,38 @@
#include "AuthService.h"
#include <optional>
#include "UserManager.h"
#include "dependency_injection/DependencyContainer.h"
#include "storages/IUserStorage.h"
AuthService::AuthService()
{
m_logger = DependencyContainer::Resolve<spdlog::logger>();
m_userStorage = DependencyContainer::Resolve<IUserStorage>();
m_userManager = DependencyContainer::Resolve<UserManager>();
}
bool AuthService::authenticateUser(
const std::string& username,
std::string&& password,
std::string& outToken
)
{
auto foundUser = m_userStorage->getUserByUsername(username);
if (!foundUser.has_value())
{
m_logger->info("Failed to find user with username `{}`", username);
}
return m_userManager->validateCredentials(foundUser.value(), std::move(password));
}
bool AuthService::registerUser(
const std::string& username,
const std::string& password,
const std::string& firstName,
const std::string& lastName,
std::string& outToken
)
{
return false;
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include <memory>
#include <string>
#include <spdlog/logger.h>
class IUserStorage;
class UserManager;
class AuthService
{
public:
AuthService();
bool authenticateUser(
const std::string& username,
std::string&& password,
std::string& outToken
);
bool registerUser(
const std::string& username,
const std::string& password,
const std::string& firstName,
const std::string& lastName,
std::string& outToken
);
private:
std::shared_ptr<spdlog::logger> m_logger;
std::shared_ptr<IUserStorage> m_userStorage;
std::shared_ptr<UserManager> m_userManager;
};

View File

@@ -0,0 +1,23 @@
#include "UserManager.h"
#include <drogon/utils/Utilities.h>
#include "dependency_injection/DependencyContainer.h"
#include "models/User.h"
#include "services/encryption/PasswordEncryptor.h"
UserManager::UserManager()
{
m_passwordEncryptor = DependencyContainer::Resolve<PasswordEncryptor>();
}
bool UserManager::validateCredentials(const User& userData, std::string&& rawPassword)
{
auto hashedPassword = hashPassword(std::move(rawPassword));
auto base64Hash = drogon::utils::base64Encode(hashedPassword.data(), hashedPassword.size());
return base64Hash == userData.passwordHash;
}
std::vector<uint8_t> UserManager::hashPassword(std::string&& rawPassword)
{
return m_passwordEncryptor->encryptPassword(std::move(rawPassword));
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
class User;
class PasswordEncryptor;
class UserManager
{
public:
UserManager();
bool validateCredentials(const User& userData, std::string&& rawPassword);
private:
std::vector<uint8_t> hashPassword(std::string&& rawPassword);
private:
std::shared_ptr<PasswordEncryptor> m_passwordEncryptor;
};

View File

@@ -0,0 +1,7 @@
#include "PasswordEncryptor.h"
std::string PasswordEncryptor::addSalt(std::string&& rawPassword)
{
rawPassword.append("suckonthesenuts");
return rawPassword;
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <vector>
class PasswordEncryptor
{
public:
std::vector<uint8_t> encryptPassword(std::string&& rawPassword)
{
std::string salted = addSalt(std::move(rawPassword));
return hash(salted);
}
private:
std::string addSalt(std::string&& rawPassword);
protected:
virtual std::vector<uint8_t> hash(const std::string& saltedPassword) = 0;
};

View File

@@ -0,0 +1,11 @@
#include "SHA256Hasher.h"
#include <vector>
#include <openssl/sha.h>
std::vector<uint8_t> SHA256Hasher::hash(const std::string& saltedPassword)
{
std::vector<uint8_t> hash(SHA256_DIGEST_LENGTH);
SHA256(reinterpret_cast<const uint8_t*>(saltedPassword.c_str()), saltedPassword.size(), hash.data());
return hash;
}

View File

@@ -0,0 +1,7 @@
#include "PasswordEncryptor.h"
class SHA256Hasher : public PasswordEncryptor
{
private:
virtual std::vector<uint8_t> hash(const std::string& saltedPassword) override;
};

View File

@@ -0,0 +1,11 @@
#pragma once
#include <optional>
#include <string>
#include "models/User.h"
class IUserStorage
{
public:
virtual std::optional<User> getUserByUsername(const std::string& username) = 0;
};

View File

@@ -0,0 +1,80 @@
#include "JsonConversion.h"
Json::Value nlohmannToJsonValue(const nlohmann::json& j) {
using nlohmann::json;
Json::Value result;
if (j.is_object()) {
for (auto& el : j.items()) {
result[el.key()] = nlohmannToJsonValue(el.value());
}
}
else if (j.is_array()) {
for (size_t i = 0; i < j.size(); ++i) {
result[Json::ArrayIndex(i)] = nlohmannToJsonValue(j[i]);
}
}
else if (j.is_string()) {
result = j.get<std::string>();
}
else if (j.is_boolean()) {
result = j.get<bool>();
}
else if (j.is_number_integer()) {
result = j.get<int>();
}
else if (j.is_number_unsigned()) {
result = j.get<unsigned int>();
}
else if (j.is_number_float()) {
result = j.get<double>();
}
else if (j.is_null()) {
result = Json::Value();
}
return result;
}
nlohmann::json jsonValueToNlohmann(const Json::Value& value) {
using nlohmann::json;
switch (value.type()) {
case Json::nullValue:
return nullptr;
case Json::intValue:
return value.asInt();
case Json::uintValue:
return value.asUInt();
case Json::realValue:
return value.asDouble();
case Json::stringValue:
return value.asString();
case Json::booleanValue:
return value.asBool();
case Json::arrayValue: {
json j_array = json::array();
for (const auto& item : value) {
j_array.push_back(jsonValueToNlohmann(item));
}
return j_array;
}
case Json::objectValue: {
json j_obj = json::object();
for (const auto& key : value.getMemberNames()) {
j_obj[key] = jsonValueToNlohmann(value[key]);
}
return j_obj;
}
default:
return nullptr;
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include <json/json.h>
#include <nlohmann/json.hpp>
Json::Value nlohmannToJsonValue(const nlohmann::json& j);
nlohmann::json jsonValueToNlohmann(const Json::Value& value);