From 1b8c28e67be33308c8a717324465ed4031ddfab4 Mon Sep 17 00:00:00 2001 From: kdeng00 Date: Tue, 24 Sep 2019 22:29:09 -0400 Subject: [PATCH] added user authentication and updated README.md --- README.md | 65 +++--- include/controller/LoginController.hpp | 73 ++++--- include/database/UserRepository.h | 59 ++++-- include/manager/UserManager.h | 24 ++- include/model/Models.h | 269 +++++++++++++------------ include/type/SaltFilter.h | 3 +- include/utility/MetadataRetriever.h | 23 +-- include/utility/PasswordEncryption.h | 14 +- src/database/UserRepository.cpp | 159 +++++++++++++++ src/manager/UserManager.cpp | 50 ++++- src/utility/PasswordEncryption.cpp | 11 + 11 files changed, 492 insertions(+), 258 deletions(-) diff --git a/README.md b/README.md index 306ae15..a6cf025 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,10 @@ Create the API and enter an approrpiate name and identified. For the identified, Replace [domain] with the domain name of the created tenant. This can be found in the Default App from the Application menu. Replace [identifier] with the identifer root name in the appsettings environment file. Not the friendly name but the root name of the identifier, omitting the http protocol and the *api* path. ```Json - "Auth0": { - "Domain": "[domain].auth0.com", - "ApiIdentifier": "https://[identifier]/api" - }, + "domain": "[domain].auth0.com", + "api_identifier": "https://[identifier]/api", + "client_id": "iamunique", + "client_secret": "Icankeepasecret" ``` For the sake of this section, I will not go over configuring the API to accept the signing algorithm since it has already been configured in the [Startip](Startup.cs).cs file. Click on permissions to create the permissions for the API. @@ -73,7 +73,7 @@ From the Application page, copy the client id and client secret. These values wi

-Enter the information in the corresponding ``authcredentials.json`` file +Enter the information in the corresponding ``authcredentials.json`` file ```Json { "domain": "somedomain.auth0.com", @@ -88,10 +88,10 @@ Enter the information in the corresponding ``authcredentials.json`` file For the purposes of properly uploading, downloading, updating, deleting, and streaming songs the API filesystem paths must be configured. For that purpose you have to open the ``paths.json`` file. What is meant by this is that the `root_music_path` directory where all music will be stored must exist. The `cover_root_path`, `archive_root_path`, and `temp_root_path` paths must exist and be accessible. An example on a Linux system: ```Json { - "root_music_path": "/home/dev/null/music/", - "temp_root_path": "/home/dev/null/music/temp/", - "cover_root_path": "/home/dev/null/music/coverArt/", - "archive_root_path": "/home/dev/null/music/archive/" + "root_music_path": "/dev/null/music/", + "temp_root_path": "/dev/null/music/temp/", + "cover_root_path": "/dev/null/music/coverArt/", + "archive_root_path": "/dev/null/music/archive/" } ``` * `root_music_path` - Where music will be stored in the following convention: *`Artist/Album/Songs`* @@ -104,55 +104,56 @@ For the purposes of properly uploading, downloading, updating, deleting, and str ### Database connection string -In order for Database functionality to be operable, there must be a valid connection string and credentials with appropriate permissions. **At the moment there is only support for MySQL**. Depending on your environment `Release` or `Debug` you will need to edit the appsettings.json or appsettings.Development.json accordingly. An example of the fields to change are below: +In order for Database functionality to be operable, there must be a valid connection string and MySQL credentials with appropriate permissions. **At the moment there is only support for MySQL**. Edit the database.json file accordingly. An example of the fields to change are below: ```Json { - "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=my_db;Uid=admin;Pwd=toughpassword;" - } + "server": "localhost", + "database": "my_db", + "username": "admin", + "password": "toughpassword" } ``` -* Server - The address or domain name of the MySQL server -* Database - The database name -* Uid - Username -* Password - Self-explanatory +* server - The address or domain name of the MySQL server +* database - The database name +* user - Username +* password - Self-explanatory The only requirement of the User is that the user should have full permissions to the database as well as permissions to create a database. Other than that, that is all that is required. -### Migrations +### Database -Prior to starting the API, the Migrations must be applied. There are 6 tables with migrations being applied and thy are: -* Users +Prior to starting the API, the database must be created. The following tables are required: +* User * Song * Album * Artist * Year * Genre +* CoverArt -There is a script for Linux systems to apply these migrations, it can be found in the [Scripts/Migrations/Linux](https://github.com/amazing-username/Icarus/blob/master/Scripts/Migrations/Linux/AddUpdate.sh) directory. Just merely execute: +There is a MySQL script to create these tables, it can be found in the [Scripts/MySQL/](https://github.com/amazing-username/Icarus/blob/master/Scripts/MySQL/create_database.sql) directory. Just merely execute: ```shell -scripts/Migrations/Linux/AddUpdate.sh +mysql -u ** -p < Scripts/MySQL/create_database.sql ``` -Or you can manually add the migrations like so for each migration: -```shell -dotnet ef migrations Add [Migration] --context [Migration]Context -``` -Then update the migrations to the database like so*: -```shell -dotnet ef database update --context [Migration]Context -``` -From this point the database has been successfully configured. Metadata and song filesystem locations can be saved. -* Will only need to execute this for UserContext and SongContext because the Song table has relational constraints with Album, Artist, Year, and Genre. +From this point the database has been successfully created. Metadata and song filesystem locations can be saved. + ## Building and Running ``` git clone --recursive https://github.com/kdeng00/icarus +cd 3rdparty/libbcrypt/ +make +cp bcrypt.a libbcrypt.a +cp bcrypt.o libbcrypt.o +cd ../.. mkdir build cd build +conan install .. cmake .. +make bin/icarus ``` Runs the server on localhost port 5002 diff --git a/include/controller/LoginController.hpp b/include/controller/LoginController.hpp index c303534..0d51079 100644 --- a/include/controller/LoginController.hpp +++ b/include/controller/LoginController.hpp @@ -11,44 +11,59 @@ #include "dto/LoginResultDto.hpp" #include "manager/TokenManager.h" +#include "manager/UserManager.h" #include "model/Models.h" -namespace controller +namespace controller { +class LoginController : public oatpp::web::server::api::ApiController { - class LoginController : public oatpp::web::server::api::ApiController - { - public: - LoginController(std::string p, OATPP_COMPONENT(std::shared_ptr, objectMapper)) - : oatpp::web::server::api::ApiController(objectMapper), exe_path(p) - { } - LoginController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr, objectMapper)) - :oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf) - { } +public: + LoginController(std::string p, OATPP_COMPONENT(std::shared_ptr, objectMapper)) + : oatpp::web::server::api::ApiController(objectMapper), exe_path(p) + { } + LoginController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr, objectMapper)) + :oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf) + { } - #include OATPP_CODEGEN_BEGIN(ApiController) + #include OATPP_CODEGEN_BEGIN(ApiController) - ENDPOINT("POST", "/api/v1/login", data, BODY_DTO(dto::UserDto::ObjectWrapper, usr)) { - OATPP_LOGI("icarus", "logging in"); + ENDPOINT("POST", "/api/v1/login", data, BODY_DTO(dto::UserDto::ObjectWrapper, usr)) { + OATPP_LOGI("icarus", "logging in"); - manager::TokenManager tok; - auto token = tok.retrieveToken(m_bConf); + manager::UserManager usrMgr(m_bConf); + auto user = manager::UserManager::userDtoConv(usr); - auto logRes = dto::LoginResultDto::createShared(); - logRes->id = 0; // TODO: change this later on to something meaningful - logRes->username = usr->username->c_str(); - logRes->token = token.accessToken.c_str(); - logRes->token_type = token.tokenType.c_str(); - logRes->expiration = token.expiration; - logRes->message = "Successful"; - - return createDtoResponse(Status::CODE_200, logRes); + if (!usrMgr.doesUserExist(user)) { + std::cout << "user does not exist" << std::endl; + return createResponse(Status::CODE_401, "invalid credentials"); } - #include OATPP_CODEGEN_END(ApiController) - private: - std::string exe_path; - model::BinaryPath m_bConf; - }; + std::cout << "user exists" << std::endl; + + if (!usrMgr.validatePassword(user)) { + std::cout << "password is not valid" << std::endl; + return createResponse(Status::CODE_401, "invalid credentials"); + } + + manager::TokenManager tok; + auto token = tok.retrieveToken(m_bConf); + + auto logRes = dto::LoginResultDto::createShared(); + logRes->id = 0; // TODO: change this later on to something meaningful or remove it + logRes->username = usr->username->c_str(); + logRes->token = token.accessToken.c_str(); + logRes->token_type = token.tokenType.c_str(); + logRes->expiration = token.expiration; + logRes->message = "Successful"; + + return createDtoResponse(Status::CODE_200, logRes); + } + + #include OATPP_CODEGEN_END(ApiController) +private: + std::string exe_path; + model::BinaryPath m_bConf; +}; } #endif diff --git a/include/database/UserRepository.h b/include/database/UserRepository.h index a99efb1..1de10a8 100644 --- a/include/database/UserRepository.h +++ b/include/database/UserRepository.h @@ -7,36 +7,51 @@ #include "database/BaseRepository.h" #include "model/Models.h" +#include "type/SaltFilter.h" #include "type/UserFilter.h" namespace database { - class UserRepository : BaseRepository { - public: - UserRepository(const model::BinaryPath&); +class UserRepository : BaseRepository { +public: + UserRepository(const model::BinaryPath&); - model::User retrieveUserRecord(model::User&, type::UserFilter); + model::User retrieveUserRecord(model::User&, type::UserFilter); + model::PassSec retrieverUserSaltRecord(model::PassSec&, type::SaltFilter); - void saveUserRecord(const model::User&); - private: - struct UserLengths; - struct UserLengths - { - unsigned long firstnameLength; - unsigned long lastnameLength; - unsigned long phoneLength; - unsigned long emailLength; - unsigned long usernameLength; - unsigned long passwordLength; - }; + bool doesUserRecordExist(const model::User&, type::UserFilter); - std::shared_ptr insertUserValues(const model::User&, std::shared_ptr); - std::shared_ptr valueBind(model::User&, std::tuple&); - std::shared_ptr fetchUserLengths(const model::User&); + void saveUserRecord(const model::User&); + void saveUserSalt(const model::PassSec&); +private: + struct UserLengths; + struct SaltLengths; - std::tuple fetchUV(); - - model::User parseRecord(MYSQL_STMT*); + struct UserLengths + { + unsigned long firstnameLength; + unsigned long lastnameLength; + unsigned long phoneLength; + unsigned long emailLength; + unsigned long usernameLength; + unsigned long passwordLength; }; + struct SaltLengths + { + unsigned long saltLength; + }; + + std::shared_ptr insertUserValues(const model::User&, std::shared_ptr); + std::shared_ptr insertSaltValues(const model::PassSec&, std::shared_ptr); + std::shared_ptr valueBind(model::User&, std::tuple&); + std::shared_ptr saltValueBind(model::PassSec&, char*); + std::shared_ptr fetchUserLengths(const model::User&); + std::shared_ptr fetchSaltLengths(const model::PassSec&); + + std::tuple fetchUV(); + + model::User parseRecord(MYSQL_STMT*); + model::PassSec parseSaltRecord(MYSQL_STMT*); +}; } diff --git a/include/manager/UserManager.h b/include/manager/UserManager.h index fa65f44..e33a538 100644 --- a/include/manager/UserManager.h +++ b/include/manager/UserManager.h @@ -6,20 +6,22 @@ #include "dto/LoginResultDto.hpp" #include "model/Models.h" -namespace manager +namespace manager { +class UserManager { - class UserManager - { - public: - UserManager(const model::BinaryPath&); +public: + UserManager(const model::BinaryPath&); - model::User userDtoConv(dto::UserDto::ObjectWrapper&); + bool doesUserExist(const model::User&); + bool validatePassword(const model::User&); - void printUser(const model::User&); - void registerUser(model::User&); - private: - model::BinaryPath m_bConf; - }; + void printUser(const model::User&); + void registerUser(model::User&); + + static model::User userDtoConv(dto::UserDto::ObjectWrapper&); +private: + model::BinaryPath m_bConf; +}; } #endif diff --git a/include/model/Models.h b/include/model/Models.h index feb4ebf..13f3789 100644 --- a/include/model/Models.h +++ b/include/model/Models.h @@ -4,162 +4,163 @@ #include #include -namespace model +namespace model { +struct Song { - struct Song + Song() = default; + Song(const int id) : id(id) { } + + int id; + std::string title; + std::string artist; + std::string album; + std::string genre; + int year; + int duration; + int track; + int disc; + std::string songPath; + std::vector data; + int coverArtId; + int artistId; + int albumId; + int genreId; + int yearId; +}; + +struct Artist +{ + Artist() = default; + Artist(const Song& song) { - Song() = default; - Song(const int id) : id(id) { } + id = song.artistId; + artist = song.artist; + } + Artist(const int id) : id(id) { } - int id; - std::string title; - std::string artist; - std::string album; - std::string genre; - int year; - int duration; - int track; - int disc; - std::string songPath; - std::vector data; - int coverArtId; - int artistId; - int albumId; - int genreId; - int yearId; - }; + int id; + std::string artist; +}; - struct Artist +struct Album +{ + Album() = default; + Album(const Song& song) { - Artist() = default; - Artist(const Song& song) - { - id = song.artistId; - artist = song.artist; - } - Artist(const int id) : id(id) { } + id = song.albumId; + title = song.album; + year = song.year; + } + Album(const int id) : id(id) { } - int id; - std::string artist; - }; + int id; + std::string title; + int year; + std::vector songs; +}; - struct Album +struct Genre +{ + Genre() = default; + Genre(const Song& song) { - Album() = default; - Album(const Song& song) - { - id = song.albumId; - title = song.album; - year = song.year; - } - Album(const int id) : id(id) { } + id = song.genreId; + category = song.genre; + } + Genre(const int id) : id(id) { } - int id; - std::string title; - int year; - std::vector songs; - }; + int id; + std::string category; - struct Genre +}; + +struct Year +{ + Year() = default; + Year(const Song& song) { - Genre() = default; - Genre(const Song& song) - { - id = song.genreId; - category = song.genre; - } - Genre(const int id) : id(id) { } + id = song.yearId; + year = song.year; + } + Year(const int id) : id(id) { } - int id; - std::string category; + int id; + int year; +}; - }; - - struct Year +struct Cover +{ + Cover() = default; + Cover(const Song& song) { - Year() = default; - Year(const Song& song) - { - id = song.yearId; - year = song.year; - } - Year(const int id) : id(id) { } + id = song.coverArtId; + songTitle = song.title; + } + Cover(const int id) : id(id) { } - int id; - int year; - }; + int id; + std::string songTitle; + std::string imagePath; + // Not being used but it should be + std::vector data; +}; - struct Cover - { - Cover() = default; - Cover(const Song& song) - { - id = song.coverArtId; - songTitle = song.title; - } - Cover(const int id) : id(id) { } +struct LoginResult +{ + int userId; + std::string username; + std::string accessToken; + std::string tokenType; + std::string message; + int expiration; +}; - int id; - std::string songTitle; - std::string imagePath; - // Not being used but it should be - std::vector data; - }; +struct User +{ + int id; + std::string firstname; + std::string lastname; + std::string email; + std::string phone; + std::string username; + std::string password; +}; - struct LoginResult - { - int userId; - std::string username; - std::string accessToken; - std::string tokenType; - std::string message; - int expiration; - }; +struct PassSec +{ + int id; + std::string hashPassword; + std::string salt; + int userId; +}; - struct User - { - int id; - std::string firstname; - std::string lastname; - std::string email; - std::string phone; - std::string username; - std::string password; - }; +struct AuthCredentials +{ + std::string domain; + std::string apiIdentifier; + std::string clientId; + std::string clientSecret; + std::string uri; + std::string endpoint; +}; - struct PassSec - { - std::string hashPassword; - std::string salt; - }; +struct DatabaseConnection +{ + std::string server; + std::string username; + std::string password; + std::string database; +}; - struct AuthCredentials - { - std::string domain; - std::string apiIdentifier; - std::string clientId; - std::string clientSecret; - std::string uri; - std::string endpoint; - }; +struct BinaryPath +{ + BinaryPath() = default; + BinaryPath(const char *p) : path(std::move(p)) { } + BinaryPath(std::string& p) : path(p) { } + BinaryPath(const std::string& p) : path(p) { } - struct DatabaseConnection - { - std::string server; - std::string username; - std::string password; - std::string database; - }; - - struct BinaryPath - { - BinaryPath() = default; - BinaryPath(const char *p) : path(std::move(p)) { } - BinaryPath(std::string& p) : path(p) { } - BinaryPath(const std::string& p) : path(p) { } - - std::string path; - }; + std::string path; +}; } #endif diff --git a/include/type/SaltFilter.h b/include/type/SaltFilter.h index b3efa14..b2e6ab5 100644 --- a/include/type/SaltFilter.h +++ b/include/type/SaltFilter.h @@ -5,7 +5,8 @@ namespace type { enum class SaltFilter { id = 0, - salt + salt, + userId }; } diff --git a/include/utility/MetadataRetriever.h b/include/utility/MetadataRetriever.h index 215fe08..ebc539e 100644 --- a/include/utility/MetadataRetriever.h +++ b/include/utility/MetadataRetriever.h @@ -6,22 +6,21 @@ #include "model/Models.h" -namespace utility +namespace utility { +class MetadataRetriever { - class MetadataRetriever - { - public: - model::Song retrieveMetadata(std::string&); +public: + model::Song retrieveMetadata(std::string&); - model::Cover updateCoverArt(const model::Song&, model::Cover&, const std::string&); - model::Cover applyStockCoverArt(const model::Song&, model::Cover&, const std::string&); - model::Cover applyCoverArt(const model::Song&, model::Cover&); + model::Cover updateCoverArt(const model::Song&, model::Cover&, const std::string&); + model::Cover applyStockCoverArt(const model::Song&, model::Cover&, const std::string&); + model::Cover applyCoverArt(const model::Song&, model::Cover&); - bool songContainsCoverArt(const model::Song&); + bool songContainsCoverArt(const model::Song&); - void updateMetadata(model::Song&, const model::Song&); - private: - }; + void updateMetadata(model::Song&, const model::Song&); +private: +}; } #endif diff --git a/include/utility/PasswordEncryption.h b/include/utility/PasswordEncryption.h index e5662db..186ad37 100644 --- a/include/utility/PasswordEncryption.h +++ b/include/utility/PasswordEncryption.h @@ -6,12 +6,14 @@ #include "model/Models.h" namespace utility { - class PasswordEncryption { - public: - model::PassSec hashPassword(const model::User&); - private: - int saltSize(); - }; +class PasswordEncryption { +public: + model::PassSec hashPassword(const model::User&); + + bool isPasswordValid(const model::User&, const model::PassSec&); +private: + int saltSize(); +}; } #endif diff --git a/src/database/UserRepository.cpp b/src/database/UserRepository.cpp index 6b5574a..dc867a7 100644 --- a/src/database/UserRepository.cpp +++ b/src/database/UserRepository.cpp @@ -38,6 +38,8 @@ model::User UserRepository::retrieveUserRecord(model::User& user, break; } + qry << " LIMIT 1"; + const auto query = qry.str(); auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); status = mysql_stmt_bind_param(stmt, params); @@ -51,7 +53,86 @@ model::User UserRepository::retrieveUserRecord(model::User& user, return user; } +model::PassSec UserRepository::retrieverUserSaltRecord(model::PassSec& userSec, type::SaltFilter filter) +{ + std::stringstream qry; + auto conn = setupMysqlConnection(); + auto stmt = mysql_stmt_init(conn); + qry << "SELECT * FROM Salt WHERE "; + + MYSQL_BIND params[1]; + std::memset(params, 0, sizeof(params)); + + switch (filter) { + case type::SaltFilter::userId: + qry << "UserId = ?"; + + params[0].buffer_type = MYSQL_TYPE_LONG; + params[0].buffer = (char*)&userSec.userId; + params[0].length = 0; + params[0].is_null = 0; + break; + default: + break; + } + + qry << " LIMIT 1"; + + const auto query = qry.str(); + auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); + status = mysql_stmt_bind_param(stmt, params); + status = mysql_stmt_execute(stmt); + + userSec = parseSaltRecord(stmt); + + mysql_stmt_close(stmt); + mysql_close(conn); + + return userSec; +} + + +bool UserRepository::doesUserRecordExist(const model::User& user, type::UserFilter filter) +{ + std::stringstream qry; + auto conn = setupMysqlConnection(); + auto stmt = mysql_stmt_init(conn); + + qry << "SELECT * FROM User WHERE "; + + MYSQL_BIND params[1]; + std::memset(params, 0, sizeof(params)); + + auto userLength = user.username.size(); + switch (filter) { + case type::UserFilter::username: + qry << "Username = ?"; + + params[0].buffer_type = MYSQL_TYPE_STRING; + params[0].buffer = (char*)user.username.c_str(); + params[0].length = &userLength; + params[0].is_null = 0; + break; + default: + break; + } + + qry << " LIMIT 1"; + + const auto query = qry.str(); + auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); + status = mysql_stmt_bind_param(stmt, params); + status = mysql_stmt_execute(stmt); + + mysql_stmt_store_result(stmt); + const auto rowCount = mysql_stmt_num_rows(stmt); + + mysql_stmt_close(stmt); + mysql_close(conn); + + return (rowCount > 0) ? true : false; +} void UserRepository::saveUserRecord(const model::User& user) { std::cout << "inserting user record" << std::endl; @@ -75,6 +156,28 @@ void UserRepository::saveUserRecord(const model::User& user) mysql_close(conn); } +void UserRepository::saveUserSalt(const model::PassSec& userSec) +{ + std::cout << "inserting user salt record" << std::endl; + auto conn = setupMysqlConnection(); + auto stmt = mysql_stmt_init(conn); + + std::stringstream qry; + qry << "INSERT INTO Salt(Salt, UserId) VALUES(?,?)"; + + const auto query = qry.str(); + + auto sizes = fetchSaltLengths(userSec); + auto values = insertSaltValues(userSec, sizes); + + auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); + status = mysql_stmt_bind_param(stmt, values.get()); + status = mysql_stmt_execute(stmt); + + mysql_stmt_close(stmt); + mysql_close(conn); +} + std::shared_ptr UserRepository::insertUserValues(const model::User& user, std::shared_ptr lengths) @@ -114,6 +217,22 @@ std::shared_ptr UserRepository::insertUserValues(const model::User& return values; } +std::shared_ptr UserRepository::insertSaltValues(const model::PassSec& passSec, + std::shared_ptr lengths) +{ + std::shared_ptr values((MYSQL_BIND*) std::calloc(6, sizeof(MYSQL_BIND))); + + values.get()[0].buffer_type = MYSQL_TYPE_STRING; + values.get()[0].buffer = (char*)passSec.hashPassword.c_str(); + values.get()[0].length = &(lengths->saltLength); + + values.get()[1].buffer_type = MYSQL_TYPE_LONG; + values.get()[1].buffer = (char*)&passSec.userId; + + + return values; +} + std::shared_ptr UserRepository::valueBind(model::User& user, std::tuple& us) { @@ -150,6 +269,24 @@ std::shared_ptr UserRepository::valueBind(model::User& user, return values; } +std::shared_ptr UserRepository::saltValueBind(model::PassSec& userSalt, char *salt) +{ + std::shared_ptr values((MYSQL_BIND*) std::calloc(3, sizeof(MYSQL_BIND))); + constexpr auto strLen = 1024; + + values.get()[0].buffer_type = MYSQL_TYPE_LONG; + values.get()[0].buffer = (char*)&userSalt.id; + + values.get()[1].buffer_type = MYSQL_TYPE_STRING; + values.get()[1].buffer = (char*)salt; + values.get()[1].buffer_length = strLen; + + values.get()[2].buffer_type = MYSQL_TYPE_LONG; + values.get()[2].buffer = (char*)&userSalt.userId; + + return values; +} + std::shared_ptr UserRepository::fetchUserLengths(const model::User& user) { auto userLen = std::make_shared(); @@ -163,6 +300,14 @@ std::shared_ptr UserRepository::fetchUserLengths(co return userLen; } +std::shared_ptr UserRepository::fetchSaltLengths(const model::PassSec& passSec) +{ + auto saltLen = std::make_shared(); + saltLen->saltLength = passSec.salt.size(); + + return saltLen; +} + std::tuple UserRepository::fetchUV() { @@ -195,4 +340,18 @@ model::User UserRepository::parseRecord(MYSQL_STMT *stmt) return user; } + +model::PassSec UserRepository::parseSaltRecord(MYSQL_STMT* stmt) +{ + model::PassSec userSalt; + char saltKey[1024]; + + auto bindedValues = saltValueBind(userSalt, saltKey); + auto status = mysql_stmt_bind_result(stmt, bindedValues.get()); + status = mysql_stmt_fetch(stmt); + + userSalt.salt = saltKey; + + return userSalt; +} } diff --git a/src/manager/UserManager.cpp b/src/manager/UserManager.cpp index 8c7940e..650f01c 100644 --- a/src/manager/UserManager.cpp +++ b/src/manager/UserManager.cpp @@ -2,26 +2,35 @@ #include "database/UserRepository.h" #include "type/UserFilter.h" +#include "type/SaltFilter.h" #include "utility/MetadataRetriever.h" #include "utility/PasswordEncryption.h" namespace manager { UserManager::UserManager(const model::BinaryPath& bConf) : m_bConf(bConf) { } - -model::User UserManager::userDtoConv(dto::UserDto::ObjectWrapper& userDto) +bool UserManager::doesUserExist(const model::User& user) { - model::User user; + database::UserRepository userRepo(m_bConf); - user.id = (userDto->userId.getPtr() == nullptr) ? 0 : userDto->userId->getValue(); - user.firstname = (userDto->firstname == nullptr) ? "" : userDto->firstname->c_str(); - user.lastname = (userDto->lastname == nullptr) ? "" : userDto->lastname->c_str(); - user.phone = (userDto->phone == nullptr) ? "" : userDto->phone->c_str(); - user.email = (userDto->email == nullptr) ? "" : userDto->email->c_str(); - user.username = (userDto->username == nullptr) ? "" : userDto->username->c_str(); - user.password = (userDto->password == nullptr) ? "" : userDto->password->c_str(); + return userRepo.doesUserRecordExist(user, type::UserFilter::username); +} - return user; +bool UserManager::validatePassword(const model::User& user) +{ + database::UserRepository userRepo(m_bConf); + model::User usr; + usr.username = user.username; + usr = userRepo.retrieveUserRecord(usr, type::UserFilter::username); + + model::PassSec userSec; + userSec.userId = usr.id; + userSec = userRepo.retrieverUserSaltRecord(userSec, type::SaltFilter::userId); + userSec.hashPassword = usr.password; + + utility::PasswordEncryption passEnc; + + return passEnc.isPasswordValid(user, userSec); } @@ -53,7 +62,26 @@ void UserManager::registerUser(model::User& user) std::cout << usr.username << std::endl; usr = usrRepo.retrieveUserRecord(usr, type::UserFilter::username); + hashed.hashPassword = usr.password; + hashed.userId = usr.id; + + usrRepo.saveUserSalt(hashed); printUser(usr); } + +model::User UserManager::userDtoConv(dto::UserDto::ObjectWrapper& userDto) +{ + model::User user; + + user.id = (userDto->userId.getPtr() == nullptr) ? 0 : userDto->userId->getValue(); + user.firstname = (userDto->firstname == nullptr) ? "" : userDto->firstname->c_str(); + user.lastname = (userDto->lastname == nullptr) ? "" : userDto->lastname->c_str(); + user.phone = (userDto->phone == nullptr) ? "" : userDto->phone->c_str(); + user.email = (userDto->email == nullptr) ? "" : userDto->email->c_str(); + user.username = (userDto->username == nullptr) ? "" : userDto->username->c_str(); + user.password = (userDto->password == nullptr) ? "" : userDto->password->c_str(); + + return user; +} } diff --git a/src/utility/PasswordEncryption.cpp b/src/utility/PasswordEncryption.cpp index 74af613..a905fa3 100644 --- a/src/utility/PasswordEncryption.cpp +++ b/src/utility/PasswordEncryption.cpp @@ -28,6 +28,17 @@ model::PassSec PasswordEncryption::hashPassword(const model::User& user) return passSec; } + +bool PasswordEncryption::isPasswordValid(const model::User& user, const model::PassSec& userSalt) +{ + std::unique_ptr passwordHashed(new char[BCRYPT_HASHSIZE]); + + bcrypt_hashpw(user.password.c_str(), userSalt.salt.c_str(), passwordHashed.get()); + + return (userSalt.hashPassword.compare(passwordHashed.get()) == 0) ? true : false; +} + + int PasswordEncryption::saltSize() { constexpr auto size = 10000;