added user authentication and updated README.md

This commit is contained in:
kdeng00
2019-09-24 22:29:09 -04:00
parent e936c2da17
commit 1b8c28e67b
11 changed files with 492 additions and 258 deletions
+33 -32
View File
@@ -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. 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 ```Json
"Auth0": { "domain": "[domain].auth0.com",
"Domain": "[domain].auth0.com", "api_identifier": "https://[identifier]/api",
"ApiIdentifier": "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. 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
<h1 align="center"> <h1 align="center">
<img src="Images/Configuration/api_cred.png" width=100%> <img src="Images/Configuration/api_cred.png" width=100%>
</h1> </h1>
Enter the information in the corresponding ``authcredentials.json`` file Enter the information in the corresponding ``authcredentials.json`` file
```Json ```Json
{ {
"domain": "somedomain.auth0.com", "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: 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 ```Json
{ {
"root_music_path": "/home/dev/null/music/", "root_music_path": "/dev/null/music/",
"temp_root_path": "/home/dev/null/music/temp/", "temp_root_path": "/dev/null/music/temp/",
"cover_root_path": "/home/dev/null/music/coverArt/", "cover_root_path": "/dev/null/music/coverArt/",
"archive_root_path": "/home/dev/null/music/archive/" "archive_root_path": "/dev/null/music/archive/"
} }
``` ```
* `root_music_path` - Where music will be stored in the following convention: *`Artist/Album/Songs`* * `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 ### 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 ```Json
{ {
"ConnectionStrings": { "server": "localhost",
"DefaultConnection": "Server=localhost;Database=my_db;Uid=admin;Pwd=toughpassword;" "database": "my_db",
} "username": "admin",
"password": "toughpassword"
} }
``` ```
* Server - The address or domain name of the MySQL server * server - The address or domain name of the MySQL server
* Database - The database name * database - The database name
* Uid - Username * user - Username
* Password - Self-explanatory * 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. 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: Prior to starting the API, the database must be created. The following tables are required:
* Users * User
* Song * Song
* Album * Album
* Artist * Artist
* Year * Year
* Genre * 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 ```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<sup>*</sup>:
```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.
<sup>*</sup> 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 ## Building and Running
``` ```
git clone --recursive https://github.com/kdeng00/icarus 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 mkdir build
cd build cd build
conan install ..
cmake .. cmake ..
make
bin/icarus bin/icarus
``` ```
Runs the server on localhost port 5002 Runs the server on localhost port 5002
+44 -29
View File
@@ -11,44 +11,59 @@
#include "dto/LoginResultDto.hpp" #include "dto/LoginResultDto.hpp"
#include "manager/TokenManager.h" #include "manager/TokenManager.h"
#include "manager/UserManager.h"
#include "model/Models.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>, objectMapper))
public: : oatpp::web::server::api::ApiController(objectMapper), exe_path(p)
LoginController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper)) { }
: oatpp::web::server::api::ApiController(objectMapper), exe_path(p) LoginController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
{ } :oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
LoginController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, 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)) { ENDPOINT("POST", "/api/v1/login", data, BODY_DTO(dto::UserDto::ObjectWrapper, usr)) {
OATPP_LOGI("icarus", "logging in"); OATPP_LOGI("icarus", "logging in");
manager::TokenManager tok; manager::UserManager usrMgr(m_bConf);
auto token = tok.retrieveToken(m_bConf); auto user = manager::UserManager::userDtoConv(usr);
auto logRes = dto::LoginResultDto::createShared(); if (!usrMgr.doesUserExist(user)) {
logRes->id = 0; // TODO: change this later on to something meaningful std::cout << "user does not exist" << std::endl;
logRes->username = usr->username->c_str(); return createResponse(Status::CODE_401, "invalid credentials");
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) std::cout << "user exists" << std::endl;
private:
std::string exe_path; if (!usrMgr.validatePassword(user)) {
model::BinaryPath m_bConf; 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 #endif
+37 -22
View File
@@ -7,36 +7,51 @@
#include "database/BaseRepository.h" #include "database/BaseRepository.h"
#include "model/Models.h" #include "model/Models.h"
#include "type/SaltFilter.h"
#include "type/UserFilter.h" #include "type/UserFilter.h"
namespace database { namespace database {
class UserRepository : BaseRepository { class UserRepository : BaseRepository {
public: public:
UserRepository(const model::BinaryPath&); 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&); bool doesUserRecordExist(const model::User&, type::UserFilter);
private:
struct UserLengths;
struct UserLengths
{
unsigned long firstnameLength;
unsigned long lastnameLength;
unsigned long phoneLength;
unsigned long emailLength;
unsigned long usernameLength;
unsigned long passwordLength;
};
std::shared_ptr<MYSQL_BIND> insertUserValues(const model::User&, std::shared_ptr<UserLengths>); void saveUserRecord(const model::User&);
std::shared_ptr<MYSQL_BIND> valueBind(model::User&, std::tuple<char*, char*, char*, char*, char*, char*>&); void saveUserSalt(const model::PassSec&);
std::shared_ptr<UserLengths> fetchUserLengths(const model::User&); private:
struct UserLengths;
struct SaltLengths;
std::tuple<char*, char*, char*, char*, char*, char*> fetchUV(); struct UserLengths
{
model::User parseRecord(MYSQL_STMT*); 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<MYSQL_BIND> insertUserValues(const model::User&, std::shared_ptr<UserLengths>);
std::shared_ptr<MYSQL_BIND> insertSaltValues(const model::PassSec&, std::shared_ptr<SaltLengths>);
std::shared_ptr<MYSQL_BIND> valueBind(model::User&, std::tuple<char*, char*, char*, char*, char*, char*>&);
std::shared_ptr<MYSQL_BIND> saltValueBind(model::PassSec&, char*);
std::shared_ptr<UserLengths> fetchUserLengths(const model::User&);
std::shared_ptr<SaltLengths> fetchSaltLengths(const model::PassSec&);
std::tuple<char*, char*, char*, char*, char*, char*> fetchUV();
model::User parseRecord(MYSQL_STMT*);
model::PassSec parseSaltRecord(MYSQL_STMT*);
};
} }
+13 -11
View File
@@ -6,20 +6,22 @@
#include "dto/LoginResultDto.hpp" #include "dto/LoginResultDto.hpp"
#include "model/Models.h" #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 printUser(const model::User&);
void registerUser(model::User&); void registerUser(model::User&);
private:
model::BinaryPath m_bConf; static model::User userDtoConv(dto::UserDto::ObjectWrapper&);
}; private:
model::BinaryPath m_bConf;
};
} }
#endif #endif
+135 -134
View File
@@ -4,162 +4,163 @@
#include <string> #include <string>
#include <vector> #include <vector>
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<unsigned char> data;
int coverArtId;
int artistId;
int albumId;
int genreId;
int yearId;
};
struct Artist
{
Artist() = default;
Artist(const Song& song)
{ {
Song() = default; id = song.artistId;
Song(const int id) : id(id) { } artist = song.artist;
}
Artist(const int id) : id(id) { }
int id; int id;
std::string title; std::string artist;
std::string artist; };
std::string album;
std::string genre;
int year;
int duration;
int track;
int disc;
std::string songPath;
std::vector<unsigned char> data;
int coverArtId;
int artistId;
int albumId;
int genreId;
int yearId;
};
struct Artist struct Album
{
Album() = default;
Album(const Song& song)
{ {
Artist() = default; id = song.albumId;
Artist(const Song& song) title = song.album;
{ year = song.year;
id = song.artistId; }
artist = song.artist; Album(const int id) : id(id) { }
}
Artist(const int id) : id(id) { }
int id; int id;
std::string artist; std::string title;
}; int year;
std::vector<Song> songs;
};
struct Album struct Genre
{
Genre() = default;
Genre(const Song& song)
{ {
Album() = default; id = song.genreId;
Album(const Song& song) category = song.genre;
{ }
id = song.albumId; Genre(const int id) : id(id) { }
title = song.album;
year = song.year;
}
Album(const int id) : id(id) { }
int id; int id;
std::string title; std::string category;
int year;
std::vector<Song> songs;
};
struct Genre };
struct Year
{
Year() = default;
Year(const Song& song)
{ {
Genre() = default; id = song.yearId;
Genre(const Song& song) year = song.year;
{ }
id = song.genreId; Year(const int id) : id(id) { }
category = song.genre;
}
Genre(const int id) : id(id) { }
int id; int id;
std::string category; int year;
};
}; struct Cover
{
struct Year Cover() = default;
Cover(const Song& song)
{ {
Year() = default; id = song.coverArtId;
Year(const Song& song) songTitle = song.title;
{ }
id = song.yearId; Cover(const int id) : id(id) { }
year = song.year;
}
Year(const int id) : id(id) { }
int id; int id;
int year; std::string songTitle;
}; std::string imagePath;
// Not being used but it should be
std::vector<unsigned char> data;
};
struct Cover struct LoginResult
{ {
Cover() = default; int userId;
Cover(const Song& song) std::string username;
{ std::string accessToken;
id = song.coverArtId; std::string tokenType;
songTitle = song.title; std::string message;
} int expiration;
Cover(const int id) : id(id) { } };
int id; struct User
std::string songTitle; {
std::string imagePath; int id;
// Not being used but it should be std::string firstname;
std::vector<unsigned char> data; std::string lastname;
}; std::string email;
std::string phone;
std::string username;
std::string password;
};
struct LoginResult struct PassSec
{ {
int userId; int id;
std::string username; std::string hashPassword;
std::string accessToken; std::string salt;
std::string tokenType; int userId;
std::string message; };
int expiration;
};
struct User struct AuthCredentials
{ {
int id; std::string domain;
std::string firstname; std::string apiIdentifier;
std::string lastname; std::string clientId;
std::string email; std::string clientSecret;
std::string phone; std::string uri;
std::string username; std::string endpoint;
std::string password; };
};
struct PassSec struct DatabaseConnection
{ {
std::string hashPassword; std::string server;
std::string salt; std::string username;
}; std::string password;
std::string database;
};
struct AuthCredentials struct BinaryPath
{ {
std::string domain; BinaryPath() = default;
std::string apiIdentifier; BinaryPath(const char *p) : path(std::move(p)) { }
std::string clientId; BinaryPath(std::string& p) : path(p) { }
std::string clientSecret; BinaryPath(const std::string& p) : path(p) { }
std::string uri;
std::string endpoint;
};
struct DatabaseConnection std::string path;
{ };
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;
};
} }
#endif #endif
+2 -1
View File
@@ -5,7 +5,8 @@ namespace type {
enum class SaltFilter enum class SaltFilter
{ {
id = 0, id = 0,
salt salt,
userId
}; };
} }
+11 -12
View File
@@ -6,22 +6,21 @@
#include "model/Models.h" #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 updateCoverArt(const model::Song&, model::Cover&, const std::string&);
model::Cover applyStockCoverArt(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 applyCoverArt(const model::Song&, model::Cover&);
bool songContainsCoverArt(const model::Song&); bool songContainsCoverArt(const model::Song&);
void updateMetadata(model::Song&, const model::Song&); void updateMetadata(model::Song&, const model::Song&);
private: private:
}; };
} }
#endif #endif
+8 -6
View File
@@ -6,12 +6,14 @@
#include "model/Models.h" #include "model/Models.h"
namespace utility { namespace utility {
class PasswordEncryption { class PasswordEncryption {
public: public:
model::PassSec hashPassword(const model::User&); model::PassSec hashPassword(const model::User&);
private:
int saltSize(); bool isPasswordValid(const model::User&, const model::PassSec&);
}; private:
int saltSize();
};
} }
#endif #endif
+159
View File
@@ -38,6 +38,8 @@ model::User UserRepository::retrieveUserRecord(model::User& user,
break; break;
} }
qry << " LIMIT 1";
const auto query = qry.str(); const auto query = qry.str();
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size()); auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
status = mysql_stmt_bind_param(stmt, params); status = mysql_stmt_bind_param(stmt, params);
@@ -51,7 +53,86 @@ model::User UserRepository::retrieveUserRecord(model::User& user,
return 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) void UserRepository::saveUserRecord(const model::User& user)
{ {
std::cout << "inserting user record" << std::endl; std::cout << "inserting user record" << std::endl;
@@ -75,6 +156,28 @@ void UserRepository::saveUserRecord(const model::User& user)
mysql_close(conn); 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<MYSQL_BIND> UserRepository::insertUserValues(const model::User& user, std::shared_ptr<MYSQL_BIND> UserRepository::insertUserValues(const model::User& user,
std::shared_ptr<UserRepository::UserLengths> lengths) std::shared_ptr<UserRepository::UserLengths> lengths)
@@ -114,6 +217,22 @@ std::shared_ptr<MYSQL_BIND> UserRepository::insertUserValues(const model::User&
return values; return values;
} }
std::shared_ptr<MYSQL_BIND> UserRepository::insertSaltValues(const model::PassSec& passSec,
std::shared_ptr<UserRepository::SaltLengths> lengths)
{
std::shared_ptr<MYSQL_BIND> 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<MYSQL_BIND> UserRepository::valueBind(model::User& user, std::shared_ptr<MYSQL_BIND> UserRepository::valueBind(model::User& user,
std::tuple<char*, char*, char*, char*, char*, char*>& us) std::tuple<char*, char*, char*, char*, char*, char*>& us)
{ {
@@ -150,6 +269,24 @@ std::shared_ptr<MYSQL_BIND> UserRepository::valueBind(model::User& user,
return values; return values;
} }
std::shared_ptr<MYSQL_BIND> UserRepository::saltValueBind(model::PassSec& userSalt, char *salt)
{
std::shared_ptr<MYSQL_BIND> 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::UserLengths> UserRepository::fetchUserLengths(const model::User& user) std::shared_ptr<UserRepository::UserLengths> UserRepository::fetchUserLengths(const model::User& user)
{ {
auto userLen = std::make_shared<UserLengths>(); auto userLen = std::make_shared<UserLengths>();
@@ -163,6 +300,14 @@ std::shared_ptr<UserRepository::UserLengths> UserRepository::fetchUserLengths(co
return userLen; return userLen;
} }
std::shared_ptr<UserRepository::SaltLengths> UserRepository::fetchSaltLengths(const model::PassSec& passSec)
{
auto saltLen = std::make_shared<SaltLengths>();
saltLen->saltLength = passSec.salt.size();
return saltLen;
}
std::tuple<char*, char*, char*, char*, char*, char*> UserRepository::fetchUV() std::tuple<char*, char*, char*, char*, char*, char*> UserRepository::fetchUV()
{ {
@@ -195,4 +340,18 @@ model::User UserRepository::parseRecord(MYSQL_STMT *stmt)
return user; 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;
}
} }
+39 -11
View File
@@ -2,26 +2,35 @@
#include "database/UserRepository.h" #include "database/UserRepository.h"
#include "type/UserFilter.h" #include "type/UserFilter.h"
#include "type/SaltFilter.h"
#include "utility/MetadataRetriever.h" #include "utility/MetadataRetriever.h"
#include "utility/PasswordEncryption.h" #include "utility/PasswordEncryption.h"
namespace manager { namespace manager {
UserManager::UserManager(const model::BinaryPath& bConf) : m_bConf(bConf) { } UserManager::UserManager(const model::BinaryPath& bConf) : m_bConf(bConf) { }
bool UserManager::doesUserExist(const model::User& user)
model::User UserManager::userDtoConv(dto::UserDto::ObjectWrapper& userDto)
{ {
model::User user; database::UserRepository userRepo(m_bConf);
user.id = (userDto->userId.getPtr() == nullptr) ? 0 : userDto->userId->getValue(); return userRepo.doesUserRecordExist(user, type::UserFilter::username);
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; 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; std::cout << usr.username << std::endl;
usr = usrRepo.retrieveUserRecord(usr, type::UserFilter::username); usr = usrRepo.retrieveUserRecord(usr, type::UserFilter::username);
hashed.hashPassword = usr.password;
hashed.userId = usr.id;
usrRepo.saveUserSalt(hashed);
printUser(usr); 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;
}
} }
+11
View File
@@ -28,6 +28,17 @@ model::PassSec PasswordEncryption::hashPassword(const model::User& user)
return passSec; return passSec;
} }
bool PasswordEncryption::isPasswordValid(const model::User& user, const model::PassSec& userSalt)
{
std::unique_ptr<char[]> 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() int PasswordEncryption::saltSize()
{ {
constexpr auto size = 10000; constexpr auto size = 10000;