From 6df48966280a2bd87b903c574e1016a8027eee11 Mon Sep 17 00:00:00 2001 From: olshab Date: Sat, 14 Jun 2025 16:35:18 +0400 Subject: [PATCH] Add token validation filter based on token signature and expiration date - changed login request from GET to POST - implement json body conversion to DTO objects - set `user_id` and `role` attributes for HttpRequest inside filter --- CMakeLists.txt | 4 + src/controllers/AuthController.cpp | 58 +++++++++---- src/controllers/AuthController.h | 13 +-- .../filters/TokenValidationFilter.cpp | 47 +++++++++++ .../filters/TokenValidationFilter.h | 18 ++++ src/dto/LoginRequestDto.h | 13 +++ src/dto/RegisterRequestDto.h | 15 ++++ .../UserStorage.cpp | 5 ++ src/mock_storage_implementation/UserStorage.h | 1 + src/services/AuthService.cpp | 56 +++++++++---- src/services/AuthService.h | 17 +--- src/services/UserManager.cpp | 84 ++++++++++++++++++- src/services/UserManager.h | 17 ++-- src/storages/IUserStorage.h | 1 + 14 files changed, 285 insertions(+), 64 deletions(-) create mode 100644 src/controllers/filters/TokenValidationFilter.cpp create mode 100644 src/controllers/filters/TokenValidationFilter.h create mode 100644 src/dto/LoginRequestDto.h create mode 100644 src/dto/RegisterRequestDto.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b687726..6e2b1cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,10 @@ add_executable(${PROJECT_NAME} src/utils/JsonConversion.h src/controllers/AuthController.cpp src/controllers/AuthController.h + src/controllers/filters/TokenValidationFilter.cpp + src/controllers/filters/TokenValidationFilter.h + src/dto/LoginRequestDto.h + src/dto/RegisterRequestDto.h src/models/Entity.h src/models/User.h src/services/AuthService.cpp diff --git a/src/controllers/AuthController.cpp b/src/controllers/AuthController.cpp index 30b0c9c..c26fdbe 100644 --- a/src/controllers/AuthController.cpp +++ b/src/controllers/AuthController.cpp @@ -2,6 +2,8 @@ #include #include "dependency_injection/DependencyContainer.h" +#include "dto/LoginRequestDto.h" +#include "dto/RegisterRequestDto.h" #include "services/AuthService.h" #include "utils/JsonConversion.h" @@ -13,37 +15,57 @@ api::v1::Auth::Auth() m_logger->info("[api::v1::Auth::Auth] Registered Auth controller"); } -void api::v1::Auth::login( +void api::v1::Auth::loginUser( const drogon::HttpRequestPtr& req, - std::function&& callback, - const std::string& username, - std::string&& password + std::function&& callback ) { - m_logger->info("[api::v1::Auth::login] User `{}` tries to login", username); + auto body = req->getBody(); + auto j = nlohmann::json::parse(body); + auto loginDataDto = j.get(); std::string token{}; - bool result = m_authService->authenticateUser(username, std::move(password), token); + bool result = m_authService->authenticateUser(loginDataDto, 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)); + m_logger->info("[api::v1::Auth::loginUser] User `{}` successfully logged in", loginDataDto.username); + auto resp = drogon::HttpResponse::newHttpResponse(); 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); + m_logger->info("[api::v1::Auth::loginUser] User `{}` failed to login", loginDataDto.username); + auto resp = drogon::HttpResponse::newHttpResponse(drogon::k401Unauthorized, drogon::ContentType::CT_TEXT_PLAIN); + resp->setBody("Invalid username or password"); + callback(resp); + } +} + +void api::v1::Auth::registerUser( + const drogon::HttpRequestPtr& req, + std::function&& callback) +{ + auto body = req->getBody(); + auto j = nlohmann::json::parse(body); + auto registerDataDto = j.get(); + + std::string token{}; + bool result = m_authService->registerUser(registerDataDto, token); + + if (result) + { + m_logger->info("[api::v1::Auth::registerUser] User `{}` successfully registered", registerDataDto.username); + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->addHeader("Authorization", "Bearer " + token); + callback(resp); + } + else + { + nlohmann::json json; + auto resp = drogon::HttpResponse::newHttpResponse(drogon::k406NotAcceptable, drogon::ContentType::CT_TEXT_PLAIN); + resp->setBody("Failed to register new account"); callback(resp); } } diff --git a/src/controllers/AuthController.h b/src/controllers/AuthController.h index da8401a..a60dbe8 100644 --- a/src/controllers/AuthController.h +++ b/src/controllers/AuthController.h @@ -12,15 +12,18 @@ class Auth : public drogon::HttpController { public: METHOD_LIST_BEGIN - METHOD_ADD(Auth::login, "/login?username={}&password={}", drogon::Get); + METHOD_ADD(Auth::loginUser, "/login", drogon::Post); + METHOD_ADD(Auth::registerUser, "/register", drogon::Post); METHOD_LIST_END Auth(); - void login(const drogon::HttpRequestPtr& req, - std::function&& callback, - const std::string& username, - std::string&& password); + void loginUser(const drogon::HttpRequestPtr& req, + std::function&& callback); + + void registerUser(const drogon::HttpRequestPtr& req, + std::function&& callback + ); private: std::shared_ptr m_logger; diff --git a/src/controllers/filters/TokenValidationFilter.cpp b/src/controllers/filters/TokenValidationFilter.cpp new file mode 100644 index 0000000..88ecc57 --- /dev/null +++ b/src/controllers/filters/TokenValidationFilter.cpp @@ -0,0 +1,47 @@ +#include "TokenValidationFilter.h" + +#include +#include "dependency_injection/DependencyContainer.h" +#include "services/UserManager.h" + +TokenValidationFilter::TokenValidationFilter() +{ + m_userManager = DependencyContainer::Resolve(); +} + +void TokenValidationFilter::doFilter( + const drogon::HttpRequestPtr& req, + drogon::FilterCallback&& fcb, + drogon::FilterChainCallback&& fccb +) +{ + auto authHeader = req->getHeader("Authorization"); + if (authHeader.empty() || authHeader.find("Bearer ") != 0) + { + auto res = drogon::HttpResponse::newHttpResponse(); + res->setStatusCode(drogon::k401Unauthorized); + res->setBody("Missing or invalid Authorization header"); + return fcb(res); + } + + std::string token = authHeader.substr(7); + bool validated = m_userManager->validateToken(token); + + if (validated) + { + // TODO: set role and user_id attributes here + auto payloadString = m_userManager->getPayloadFromToken(token); + auto payload = nlohmann::json::parse(payloadString); + + int user_id = payload["user_id"]; + std::string role = payload["role"]; + req->attributes()->insert("user_id", user_id); + req->attributes()->insert("role", role); + + return fccb(); + } + auto res = drogon::HttpResponse::newHttpResponse(); + res->setStatusCode(drogon::k401Unauthorized); + res->setBody("Invalid token"); + return fcb(res); +} diff --git a/src/controllers/filters/TokenValidationFilter.h b/src/controllers/filters/TokenValidationFilter.h new file mode 100644 index 0000000..d7f6060 --- /dev/null +++ b/src/controllers/filters/TokenValidationFilter.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class UserManager; + +class TokenValidationFilter : public drogon::HttpFilter +{ +public: + TokenValidationFilter(); + + virtual void doFilter(const drogon::HttpRequestPtr& req, + drogon::FilterCallback&& fcb, + drogon::FilterChainCallback&& fccb) override; + +private: + std::shared_ptr m_userManager; +}; diff --git a/src/dto/LoginRequestDto.h b/src/dto/LoginRequestDto.h new file mode 100644 index 0000000..199ccb3 --- /dev/null +++ b/src/dto/LoginRequestDto.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class LoginRequestDto +{ +public: + std::string username; + std::string password; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(LoginRequestDto, username, password); +}; diff --git a/src/dto/RegisterRequestDto.h b/src/dto/RegisterRequestDto.h new file mode 100644 index 0000000..706d43f --- /dev/null +++ b/src/dto/RegisterRequestDto.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class RegisterRequestDto +{ +public: + std::string username; + std::string password; + std::string firstName; + std::string lastName; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(RegisterRequestDto, username, password, firstName, lastName); +}; diff --git a/src/mock_storage_implementation/UserStorage.cpp b/src/mock_storage_implementation/UserStorage.cpp index 01b5986..d7a7cfc 100644 --- a/src/mock_storage_implementation/UserStorage.cpp +++ b/src/mock_storage_implementation/UserStorage.cpp @@ -12,3 +12,8 @@ std::optional MockUserStorage::getUserByUsername(const std::string& userna return ret; } + +bool MockUserStorage::insertUser(const User& user) +{ + return true; +} diff --git a/src/mock_storage_implementation/UserStorage.h b/src/mock_storage_implementation/UserStorage.h index 5a3e30f..8980c0c 100644 --- a/src/mock_storage_implementation/UserStorage.h +++ b/src/mock_storage_implementation/UserStorage.h @@ -6,4 +6,5 @@ class MockUserStorage : public IUserStorage { public: virtual std::optional getUserByUsername(const std::string& username) override; + virtual bool insertUser(const User& user) override; }; diff --git a/src/services/AuthService.cpp b/src/services/AuthService.cpp index 6e6dd62..b467c65 100644 --- a/src/services/AuthService.cpp +++ b/src/services/AuthService.cpp @@ -3,6 +3,8 @@ #include #include "UserManager.h" #include "dependency_injection/DependencyContainer.h" +#include "dto/LoginRequestDto.h" +#include "dto/RegisterRequestDto.h" #include "storages/IUserStorage.h" AuthService::AuthService() @@ -12,27 +14,45 @@ AuthService::AuthService() m_userManager = DependencyContainer::Resolve(); } -bool AuthService::authenticateUser( - const std::string& username, - std::string&& password, - std::string& outToken -) +bool AuthService::authenticateUser(LoginRequestDto& loginData, std::string& outToken) { - auto foundUser = m_userStorage->getUserByUsername(username); + auto foundUser = m_userStorage->getUserByUsername(loginData.username); if (!foundUser.has_value()) { - m_logger->info("Failed to find user with username `{}`", username); + m_logger->info("Failed to find user with username `{}`", loginData.username); + } + + if (m_userManager->validateCredentials(*foundUser, std::move(loginData.password))) + { + outToken = m_userManager->generateToken(*foundUser); + return true; } - 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; } + +bool AuthService::registerUser(RegisterRequestDto& registerData, std::string& outToken) +{ + if (!m_userManager->validateRegisterData(registerData)) + { + return false; + } + + auto hashedPassword = m_userManager->hashPassword(std::move(registerData.password)); + + User user; + user.username = registerData.username; + user.passwordHash = hashedPassword; + user.firstName = registerData.firstName; + user.lastName = registerData.lastName; + user.role = "LIVER"; + + bool insertResult = m_userStorage->insertUser(user); + if (!insertResult) + { + m_logger->error("Failed to create new user"); + return false; + } + + outToken = m_userManager->generateToken(user); + return true; +} diff --git a/src/services/AuthService.h b/src/services/AuthService.h index 2f7f805..4200576 100644 --- a/src/services/AuthService.h +++ b/src/services/AuthService.h @@ -6,25 +6,16 @@ class IUserStorage; class UserManager; +class LoginRequestDto; +class RegisterRequestDto; 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 - ); + bool authenticateUser(LoginRequestDto& loginData, std::string& outToken); + bool registerUser(RegisterRequestDto& registerData, std::string& outToken); private: std::shared_ptr m_logger; diff --git a/src/services/UserManager.cpp b/src/services/UserManager.cpp index f5aa3e0..8cfe650 100644 --- a/src/services/UserManager.cpp +++ b/src/services/UserManager.cpp @@ -1,23 +1,99 @@ #include "UserManager.h" +#include +#include #include +#include +#include #include "dependency_injection/DependencyContainer.h" #include "models/User.h" #include "services/encryption/PasswordEncryptor.h" +std::string PUBLIC_KEY{ +R"(-----BEGIN PUBLIC KEY----- +MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGlyZf3ilQWS862UM0wPL9t+B/La +E4QiLOWULVmiHvtg6ly5erX4LeKX9og0LYgdUyialjPRElutRq1dTEJ4x9kgDkT4 +o+hwcem+OHKkdbIfGSIdrI1SzWPYq8F9JjL9elPghD8Rul1US+2HWyrJkw3YiAjU +gmZ8mUpu5Hkq+LjzAgMBAAE= +-----END PUBLIC KEY-----)" +}; + +std::string PRIVATE_KEY{ +R"(-----BEGIN RSA PRIVATE KEY----- +MIICWgIBAAKBgGlyZf3ilQWS862UM0wPL9t+B/LaE4QiLOWULVmiHvtg6ly5erX4 +LeKX9og0LYgdUyialjPRElutRq1dTEJ4x9kgDkT4o+hwcem+OHKkdbIfGSIdrI1S +zWPYq8F9JjL9elPghD8Rul1US+2HWyrJkw3YiAjUgmZ8mUpu5Hkq+LjzAgMBAAEC +gYATCsimV5OnxQjiUMAOvTNcRN80mPMrSmynLOIfrKpBpRfNlOU3FVFb+StZH2sV +iI4q5/e19cyF5726sv2Bh4Q3Uob6eJZfpcReScUtbhFLhikCSTvWf6g+6ebCBF9m +HoXQBvJhthRo+9BSoweZjxNZirxO1DaqbrxIdufpBJL9aQJBAL/iyzb7mQ/oq8/j +BcVnPlOBK6R3q5ZcynJTgyLMTldAwTB4WZrUPBWt51WUL7LoZhMtQIGNaqApSwyp +ehaRKscCQQCMre5E5QXDvAITYIoGNs1x5qVUzKCYXYIv0hdko21V9GLNaYWLL5ux +BAZsvNLBE2DMbl4mJiL198qJItYk0nR1AkAIXjGSgkJYiUME29eiljAHoDhxAa7/ +7af+eFndqJ85+t7x6C2wLNU59M2D0+SIns3kxDJt8+bUeTiGotVqKoZ9AkAELNFK +eCWQpo7FNnNCNfQo8jhr6NrHStcnRivtj7AaAfPAtuYAuHv9Z+os5fm3QzT3PDtN +FIqrFByNr1v9ocVVAkAWvQ4q4Mkw8u/zCAuPiK9NJn377nc4bENQkcn6ZJ4dbMgt +yV8QswCTqtfaXKyOEs46WNYhEtMahiLPeL7V23Mr +-----END RSA PRIVATE KEY-----)" +}; + UserManager::UserManager() { + m_logger = DependencyContainer::Resolve(); m_passwordEncryptor = DependencyContainer::Resolve(); } 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; + return hashedPassword == userData.passwordHash; } -std::vector UserManager::hashPassword(std::string&& rawPassword) +bool UserManager::validateRegisterData(const RegisterRequestDto& registerData) { - return m_passwordEncryptor->encryptPassword(std::move(rawPassword)); + return true; +} + +std::string UserManager::generateToken(const User& userData) +{ + return jwt::create() + .set_type("JWT") + .set_payload_claim("user_id", jwt::claim(userData.id)) + .set_payload_claim("role", jwt::claim(userData.role)) + .set_expires_in(std::chrono::seconds{ 3600 }) + .sign(jwt::algorithm::rs256{ "", PRIVATE_KEY }); +} + +std::string UserManager::hashPassword(std::string&& rawPassword) +{ + auto hashData = m_passwordEncryptor->encryptPassword(std::move(rawPassword)); + return drogon::utils::base64Encode(hashData.data(), hashData.size()); +} + +bool UserManager::validateToken(const std::string& token) +{ + try + { + auto decoded = jwt::decode(token); + + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs256{ PUBLIC_KEY, "" }); + verifier.verify(decoded); + } + catch (const jwt::error::token_verification_exception& ex) + { + m_logger->info("Failed to validate token: {}", ex.what()); + return false; + } + return true; +} + +std::string UserManager::getPayloadFromToken(const std::string& token) +{ + try + { + auto decoded = jwt::decode(token); + return decoded.get_payload(); + } + catch (const std::exception& ex) + { } } diff --git a/src/services/UserManager.h b/src/services/UserManager.h index 183731f..9246d1e 100644 --- a/src/services/UserManager.h +++ b/src/services/UserManager.h @@ -2,11 +2,11 @@ #include #include -#include - -class User; +#include class PasswordEncryptor; +class RegisterRequestDto; +class User; class UserManager { @@ -14,10 +14,15 @@ public: UserManager(); bool validateCredentials(const User& userData, std::string&& rawPassword); + bool validateRegisterData(const RegisterRequestDto& registerData); + + std::string generateToken(const User& userData); + std::string hashPassword(std::string&& rawPassword); + + bool validateToken(const std::string& token); + std::string getPayloadFromToken(const std::string& token); private: - std::vector hashPassword(std::string&& rawPassword); - -private: + std::shared_ptr m_logger; std::shared_ptr m_passwordEncryptor; }; diff --git a/src/storages/IUserStorage.h b/src/storages/IUserStorage.h index 494c489..3089817 100644 --- a/src/storages/IUserStorage.h +++ b/src/storages/IUserStorage.h @@ -8,4 +8,5 @@ class IUserStorage { public: virtual std::optional getUserByUsername(const std::string& username) = 0; + virtual bool insertUser(const User& user) = 0; };