10 Commits

10 changed files with 134 additions and 136 deletions
+2
View File
@@ -48,6 +48,8 @@ add_executable(App ./src/main.cpp
./src/DAO/MemoryAuthDAO.cpp
./src/DAO/MemoryAuthDAO.h
./src/endpoints_handlers/AuthLogoutExecutor.h
./src/exceptions/session_exception.cpp
./src/exceptions/session_exception.h
)
target_link_libraries(App PRIVATE Boost::boost Boost::json Threads::Threads mysql::concpp)
+6 -3
View File
@@ -10,10 +10,13 @@
- ~~Перевести GetByUUID GetByLogin на const ref/string_view в IUserDAO - также не vector, а span(погуглить)~~ - span не применим
- ~~Привести к единому виду функции IUserDAO~~
- ~~Пройтись по коду и максимально наставить const~~
- Указать возможные исключения в интерфейсах DAO
- ~~Указать возможные исключения в интерфейсах DAO - почему может выбросить исключение~~
- ~~Вынести User в структуру. Hashed Password структура должна изначально состоять в другой структуре~~
- ~~SharedPtr - передавать по константной ссылке.~~
- Вынести обработку исключений в RootExecutor
- ~~Вынести обработку исключений в RootExecutor~~
- ~~Уменьшить дублирование кода в исключениях~~
- Покрыть логами
- ~~Сделать один класс исключений, имеющих метод HTTP code - код и сообщение записывать уже в ловушке~~
- Сделать интеграционный тест по ручкам
# UseCase'ы приложения:
@@ -141,7 +144,7 @@ null
```
##### Errors
* `401 BAD_CREDENTIALS` — Такого токена не существует(B3)
* `400 BAD_REQUEST` — Такого токена не существует(B3)
### 10. Используемые сущности ДБ
* users(uuid(PK), login(unique), hashed_password)
+2 -2
View File
@@ -27,10 +27,10 @@ bool MemoryAuthDAO::HasAuthorized(const std::string& auth_token)
bool MemoryAuthDAO::Logout(const std::string& auth_token)
{
string user_uuid = auth_tokens_to_users_uuids_[auth_token];
if (!HasAuthorized(auth_token)) return false;
string user_uuid = auth_tokens_to_users_uuids_[auth_token];
users_uuids_to_auth_tokens_.erase(user_uuid);
auth_tokens_to_users_uuids_.erase(auth_token);
+8 -6
View File
@@ -21,7 +21,7 @@ string MySQLUserDAO::Create(const user& created_user)
const std::string uuid_str = boost::uuids::to_string(uuid);
const string sql_script =
static const string sql_script =
"INSERT INTO `up_and_down`.`users` (`uuid`, `login`, `hashed_password`) VALUES (?, ?, ?);"s;
session_.
@@ -33,7 +33,7 @@ string MySQLUserDAO::Create(const user& created_user)
optional<user> MySQLUserDAO::GetByUUID(const string& uuid)
{
const string sql_script = "SELECT * FROM `up_and_down`.`users` WHERE (uuid = ?) LIMIT 1;"s;
static const string sql_script = "SELECT * FROM `up_and_down`.`users` WHERE (uuid = ?) LIMIT 1;"s;
mysqlx::SqlResult sql_result = session_.
sql(sql_script)
.bind(uuid)
@@ -44,7 +44,7 @@ optional<user> MySQLUserDAO::GetByUUID(const string& uuid)
optional<user> MySQLUserDAO::GetByLogin(const string& login)
{
const std::string sql_script = "SELECT * FROM `up_and_down`.`users` WHERE (login = ?) LIMIT 1;"s;
static const std::string sql_script = "SELECT * FROM `up_and_down`.`users` WHERE (login = ?) LIMIT 1;"s;
mysqlx::SqlResult sql_result = session_.
sql(sql_script)
.bind(login)
@@ -55,8 +55,10 @@ optional<user> MySQLUserDAO::GetByLogin(const string& login)
pair<bool, vector<user>> MySQLUserDAO::GetAll(size_t limit, size_t offset)
{
static const string sql_script = "SELECT * FROM `up_and_down`.`users` LIMIT ? OFFSET ?;"s;
mysqlx::SqlResult sql_result = session_
.sql("SELECT * FROM `up_and_down`.`users` LIMIT ? OFFSET ?;"s)
.sql(sql_script)
.bind(limit, offset)
.execute();
list<mysqlx::Row> rows = sql_result.fetchAll();
@@ -96,7 +98,7 @@ pair<bool, vector<user>> MySQLUserDAO::GetAll(size_t limit, size_t offset)
bool MySQLUserDAO::Update(const user& u)
{
const string sql_script = "UPDATE `up_and_down`.`users` SET `login` = ? WHERE `uuid` = ?;"s;
static const string sql_script = "UPDATE `up_and_down`.`users` SET `login` = ? WHERE `uuid` = ?;"s;
auto schema = session_.sql(sql_script)
.bind(u.login, u.uuid)
@@ -107,7 +109,7 @@ bool MySQLUserDAO::Update(const user& u)
bool MySQLUserDAO::Delete(const string& uuid)
{
const string sql_script = "DELETE FROM `up_and_down`.`users` WHERE `uuid` = ?;";
static const string sql_script = "DELETE FROM `up_and_down`.`users` WHERE `uuid` = ?;";
auto schema = session_.sql(sql_script)
.bind(uuid)
+32 -51
View File
@@ -10,6 +10,7 @@
#include "../DAO/IUserDAO.h"
#include "../DAO/IAuthDAO.h"
#include "../helpers/helpers.h"
#include "../exceptions/session_exception.h"
namespace uad
{
@@ -39,65 +40,45 @@ public:
const auto body = req.body();
value req_json;
value response_body;
response_body.emplace_object();
try
{
req_json = json::parse(body);
const std::string login = req_json.as_object().at("login").as_string().c_str();
const std::string password = req_json.as_object().at("password").as_string().c_str();
if (login.empty() || password.empty())
{
http::response<ResponseType> res{http::status::unprocessable_entity, req.version()};
response_body.as_object().emplace("Result", "Login or password are empty");
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
const std::optional<user> maybe_user = user_dao_->GetByLogin(login);
if (!maybe_user.has_value() || maybe_user.value().hashed_password != HashPassword(password))
{
http::response<ResponseType> res{http::status::unprocessable_entity, req.version()};
response_body.as_object().emplace("Result", "Incorrect login or password");
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
const std::string token = GenerateUUID();
auth_dao_->Login(maybe_user.value().uuid, token);
http::response<ResponseType> res{http::status::ok, req.version()};
response_body.as_object().emplace("token", token);
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
catch (const system::system_error& err)
{
http::response<ResponseType> res{http::status::bad_request, req.version()};
response_body.as_object().emplace("Result", "cannot deserialize json");
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
throw session_exception(http::status::bad_request, "cannot deserialize json");
}
const std::string login = req_json.as_object().at("login").as_string().c_str();
const std::string password = req_json.as_object().at("password").as_string().c_str();
if (login.empty() || password.empty())
{
throw session_exception(http::status::unprocessable_entity, "Login or password are empty"s);
}
const std::optional<user> maybe_user = user_dao_->GetByLogin(login);
if (!maybe_user.has_value() && maybe_user.value().hashed_password != HashPassword(password))
{
throw session_exception(http::status::forbidden,"Incorrect login or password");
}
const std::string token = GenerateUUID();
auth_dao_->Login(maybe_user.value().uuid, token);
http::response<ResponseType> res{http::status::ok, req.version()};
value response_body;
response_body.emplace_object();
response_body.as_object().emplace("token", token);
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
};
}
+19 -37
View File
@@ -21,14 +21,14 @@ class AuthLogoutExecutor : public IExecutor<Body, Allocator, ResponseType>
public:
AuthLogoutExecutor(mysqlx::Session& session,
const std::shared_ptr<IAuthDAO>& auth_dao)
: session_(session), auth_dao_(auth_dao)
const std::shared_ptr<IAuthDAO>& auth_dao) :
session_(session), auth_dao_(auth_dao)
{
}
boost::beast::http::response<ResponseType> operator ()(
boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>>&& req
) override
) override
{
using namespace boost;
using namespace boost::json;
@@ -38,47 +38,29 @@ public:
const auto body = req.body();
value req_json;
value response_body;
response_body.emplace_object();
try
{
req_json = json::parse(body);
const std::string token = req_json.as_object().at("token").as_string().c_str();
if (!auth_dao_->Logout(token))
{
http::response<ResponseType> res{http::status::bad_request, req.version()};
response_body.as_object().emplace("Result", "token is not authorized");
res.body() = json::serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
http::response<ResponseType> res{http::status::ok, req.version()};
res.body() = "true"s;
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
catch (const system::system_error& err)
{
http::response<ResponseType> res{http::status::bad_request, req.version()};
response_body.as_object().emplace("Result", "cannot deserialize json");
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
throw session_exception(http::status::internal_server_error, "cannot deserialize json"s);
}
const std::string token = req_json.as_object().at("token").as_string().c_str();
if (!auth_dao_->Logout(token))
{
throw session_exception(http::status::bad_request, "token is not authorized"s);
}
http::response<ResponseType> res{http::status::ok, req.version()};
res.body() = "null"s;
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
};
}
@@ -3,11 +3,11 @@
#include <regex>
#include <boost/json.hpp>
#include <mysqlx/xdevapi.h>
#include <mysqlx/common/api.h>
#include <boost/uuid.hpp>
#include <boost/log/trivial.hpp>
#include "IExecutor.h"
#include "../DAO/IUserDAO.h"
#include "../exceptions/session_exception.h"
namespace uad
{
@@ -35,9 +35,6 @@ public:
const auto& body = req.body();
value req_json;
value response_body;
response_body.emplace_object();
try
{
@@ -45,14 +42,7 @@ public:
}
catch (const system::system_error& err)
{
http::response<ResponseType> res{http::status::bad_request, req.version()};
response_body.as_object().emplace("Result", "cannot deserialize json");
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
throw session_exception(http::status::bad_request, "cannot deserialize json");
}
const std::string login = req_json.as_object().at("login").as_string().c_str();
@@ -60,34 +50,15 @@ public:
if (!ValidateLogin(login) || !ValidatePassword(password))
{
http::response<ResponseType> res{http::status::unprocessable_entity, req.version()};
response_body.as_object().emplace(
"Result",
"Validations failed. Login should have length from 3 to 50. Password from 5 characters length."
throw session_exception(
http::status::unprocessable_entity,
"Validations failed. Login should have length from 3 to 50. Password from 5 characters length."s
);
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
}
if (user_dao_->GetByLogin(login).has_value())
{
http::response<ResponseType> res{http::status::conflict, req.version()};
response_body.as_object().emplace(
"Result",
"user with login "s + login + " exists"s
);
res.body() = serialize(response_body);
res.set(http::field::content_type, "application/json");
res.content_length(res.body().size());
return res;
throw session_exception(http::status::conflict, "user with login "s + login + " exists"s);
}
user user;
@@ -100,6 +71,9 @@ public:
http::response<ResponseType> res{
http::status::created, req.version()
};
value response_body;
response_body.emplace_object();
response_body.as_object().emplace(
"uuid",
+24 -1
View File
@@ -1,3 +1,6 @@
#include <mysqlx/xdevapi.h>
#include <mysqlx/common/api.h>
#include "IExecutor.h"
#include "IController.h"
#include "Controller.h"
@@ -7,6 +10,7 @@
#include "../DAO/IUserDAO.h"
#include "../DAO/IAuthDAO.h"
#include "./../helpers/helpers.h"
#include "./../exceptions/session_exception.h"
namespace uad
{
@@ -84,7 +88,26 @@ public:
{
IRouteExecutor& executor = *maybe_executor_ptr.value();
return send(executor(std::move(req)));
try
{
boost::beast::http::response<ResponseType> res = executor(std::move(req));
return send(std::move(res));
}
catch (const session_exception& e)
{
boost::beast::http::response<ResponseType> res{e.code, req.version()};
boost::json::value response_body;
response_body.emplace_object();
response_body.as_object().emplace("Result", e.what());
res.body() = serialize(response_body);
res.set(boost::beast::http::field::content_type, "application/json");
res.content_length(res.body().size());
return send(std::move(res));
}
}
}
+11
View File
@@ -0,0 +1,11 @@
#include "session_exception.h"
using namespace std;
namespace uad
{
char const* session_exception::what() const
{
return message.c_str();
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <boost/beast/http/status.hpp>
#include <string>
#include <boost/exception/to_string.hpp>
namespace uad
{
struct session_exception : std::exception
{
const boost::beast::http::status code;
const std::string comment;
const std::string message;
session_exception(const boost::beast::http::status ec, const std::string info)
: code(ec), comment(info), message(std::to_string(static_cast<uint64_t>(ec)) + " - " + comment)
{}
char const* what() const override;
};
}