Implement authentication
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
27
src/main.cpp
27
src/main.cpp
@@ -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;
|
||||
}
|
||||
|
||||
14
src/mock_storage_implementation/UserStorage.cpp
Normal file
14
src/mock_storage_implementation/UserStorage.cpp
Normal 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;
|
||||
}
|
||||
9
src/mock_storage_implementation/UserStorage.h
Normal file
9
src/mock_storage_implementation/UserStorage.h
Normal 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
11
src/models/Entity.h
Normal 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
16
src/models/User.h
Normal 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);
|
||||
};
|
||||
38
src/services/AuthService.cpp
Normal file
38
src/services/AuthService.cpp
Normal 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;
|
||||
}
|
||||
33
src/services/AuthService.h
Normal file
33
src/services/AuthService.h
Normal 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;
|
||||
};
|
||||
23
src/services/UserManager.cpp
Normal file
23
src/services/UserManager.cpp
Normal 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));
|
||||
}
|
||||
23
src/services/UserManager.h
Normal file
23
src/services/UserManager.h
Normal 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;
|
||||
};
|
||||
7
src/services/encryption/PasswordEncryptor.cpp
Normal file
7
src/services/encryption/PasswordEncryptor.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "PasswordEncryptor.h"
|
||||
|
||||
std::string PasswordEncryptor::addSalt(std::string&& rawPassword)
|
||||
{
|
||||
rawPassword.append("suckonthesenuts");
|
||||
return rawPassword;
|
||||
}
|
||||
20
src/services/encryption/PasswordEncryptor.h
Normal file
20
src/services/encryption/PasswordEncryptor.h
Normal 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;
|
||||
};
|
||||
11
src/services/encryption/SHA256Hasher.cpp
Normal file
11
src/services/encryption/SHA256Hasher.cpp
Normal 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;
|
||||
}
|
||||
7
src/services/encryption/SHA256Hasher.h
Normal file
7
src/services/encryption/SHA256Hasher.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "PasswordEncryptor.h"
|
||||
|
||||
class SHA256Hasher : public PasswordEncryptor
|
||||
{
|
||||
private:
|
||||
virtual std::vector<uint8_t> hash(const std::string& saltedPassword) override;
|
||||
};
|
||||
11
src/storages/IUserStorage.h
Normal file
11
src/storages/IUserStorage.h
Normal 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;
|
||||
};
|
||||
80
src/utils/JsonConversion.cpp
Normal file
80
src/utils/JsonConversion.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
7
src/utils/JsonConversion.h
Normal file
7
src/utils/JsonConversion.h
Normal 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);
|
||||
Reference in New Issue
Block a user