Add some functionality

new handler for Login

add errors
This commit is contained in:
2026-05-03 23:04:35 +03:00
parent 0502bd62a4
commit c3f6d71c71
7 changed files with 213 additions and 45 deletions
BIN
View File
Binary file not shown.
+15 -3
View File
@@ -41,13 +41,25 @@ int Application::run() const {
MigrationsRunner runner(db); MigrationsRunner runner(db);
runner.run_file("migrations/001_init.sql"); runner.run_file("migrations/001_init.sql");
UserRepository user_repository(db); UserRepository user_repository(db);
PasswordHasher password_hasher; PasswordHasher password_hasher;
AuthService auth_service(user_repository, password_hasher); SessionRepository session(db);
SessionTokenService token_service;
AuthService auth_service(user_repository, session, password_hasher, token_service,
settings_.session_ttl);
HealthController health_controller; HealthController health_controller;
AuthController auth_controller(auth_service); AuthController auth_controller(auth_service);
drogon::app().registerHandler(
"/login",
[&auth_controller](const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
auth_controller.login(req, std::move(callback));
},
{ drogon::Post });
drogon::app().registerHandler( drogon::app().registerHandler(
"/register", "/register",
[&auth_controller](const drogon::HttpRequestPtr &req, [&auth_controller](const drogon::HttpRequestPtr &req,
+85 -24
View File
@@ -1,13 +1,80 @@
#include "http/controllers/auth_controller.hpp" #include "http/controllers/auth_controller.hpp"
#include "service/auth_errors.hpp"
#include "service/auto_service.hpp" #include "service/auto_service.hpp"
#include <drogon/drogon.h> #include <drogon/drogon.h>
#include <json/json.h> #include <json/json.h>
AuthController::AuthController(AuthService &auth_service) AuthController::AuthController(AuthService &auth_service)
: authService_(auth_service) {} : auto_servise_(auth_service) {}
void AuthController::login(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const {
const auto json = req->getJsonObject();
if(!isValidAuthJson(json)) {
Json::Value error;
error["error"]["code"] = "invalid_request";
error["error"]["message"] = "Expected JSON body with email and password";
auto responce = drogon::HttpResponse::newHttpJsonResponse(error);
responce->setStatusCode(drogon::k400BadRequest);
callback(responce);
return;
}
try {
const auto result = auto_servise_.login(LoginCommand{
.email = (*json)["email"].asString(),
.password = (*json)["password"].asString(),
});
Json::Value body;
body["message"] = "ok";
auto response = drogon::HttpResponse::newHttpJsonResponse(body);
response->setStatusCode(drogon::k200OK);
drogon::Cookie cookie("sid", result.session_token);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setSameSite(drogon::Cookie::SameSite::kLax);
response->addCookie(cookie);
callback(response);
} catch(const InvalidCredentialsError &ex) {
Json::Value error;
error["error"]["code"] = "invalid_credentials";
error["error"]["message"] = ex.what();
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k401Unauthorized);
callback(response);
} catch(const UnauthorizedError &ex) {
Json::Value error;
error["error"]["code"] = "unauthorized";
error["error"]["message"] = ex.what();
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k403Forbidden);
callback(response);
} catch(const ValidationError &ex) {
Json::Value error;
error["error"]["code"] = "validation_error";
error["error"]["message"] = ex.what();
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k400BadRequest);
callback(response);
} catch(const std::exception &ex) {
Json::Value error;
error["error"]["code"] = "internal_error";
error["error"]["message"] = "Internal server error";
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k500InternalServerError);
callback(response);
}
}
void AuthController::register_user( void AuthController::register_user(
const drogon::HttpRequestPtr &req, const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const { std::function<void(const drogon::HttpResponsePtr &)> &&callback) const {
@@ -24,7 +91,7 @@ void AuthController::register_user(
} }
try { try {
const auto result = authService_.register_user(RegisterCommand{ const auto result = auto_servise_.register_user(RegisterCommand{
.email = (*json)["email"].asString(), .email = (*json)["email"].asString(),
.password = (*json)["password"].asString(), .password = (*json)["password"].asString(),
}); });
@@ -37,30 +104,24 @@ void AuthController::register_user(
responce->setStatusCode(drogon::k201Created); responce->setStatusCode(drogon::k201Created);
callback(responce); callback(responce);
} catch(const EmailAlreadyExistsError &ex) {
Json::Value error;
error["error"]["message"] = ex.what();
error["error"]["code"] = "email_already_exists";
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k409Conflict);
} catch(const ValidationError &ex) {
Json::Value error;
error["error"]["message"] = ex.what();
error["error"]["code"] = "validation_error";
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
callback(response);
} catch(const std::exception &ex) { } catch(const std::exception &ex) {
Json::Value error; Json::Value error;
error["error"]["message"] = ex.what(); error["error"]["message"] = ex.what();
error["error"]["code"] = "internal_error";
const std::string message = ex.what();
if(message == "Email already exists") {
error["error"]["code"] = "email_already_exists";
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k409Conflict);
callback(response);
return;
}
if(message == "Invalid email" || message == "Invalid password") {
error["error"]["code"] = "validation_error";
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
callback(response);
return;
}
error["error"]["code"] = "internal_error";
auto response = drogon::HttpResponse::newHttpJsonResponse(error); auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k500InternalServerError); response->setStatusCode(drogon::k500InternalServerError);
@@ -71,4 +132,4 @@ void AuthController::register_user(
bool AuthController::isValidAuthJson(const std::shared_ptr<Json::Value> &req) const { bool AuthController::isValidAuthJson(const std::shared_ptr<Json::Value> &req) const {
return req != nullptr || req->isMember("email") || req->isMember("password") || return req != nullptr || req->isMember("email") || req->isMember("password") ||
(*req)["email"].isString() || (*req)["password"].isString(); (*req)["email"].isString() || (*req)["password"].isString();
} }
+7 -3
View File
@@ -12,9 +12,13 @@ public:
void register_user(const drogon::HttpRequestPtr &req, void register_user(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const; std::function<void(const drogon::HttpResponsePtr &)> &&callback) const;
private: void login(const drogon::HttpRequestPtr &req,
bool isValidAuthJson(const std::shared_ptr<Json::Value> &req) const; std::function<void(const drogon::HttpResponsePtr &)> &&callback) const;
private: private:
AuthService &authService_; bool isValidAuthJson(const std::shared_ptr<Json::Value> &req) const;
bool isValidLoginJson(const std::shared_ptr<Json::Value> &req) const;
private:
AuthService &auto_servise_;
}; };
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <stdexcept>
class ValidationError: public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class EmailAlreadyExistsError: public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class InvalidCredentialsError: public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class UnauthorizedError: public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
+59 -11
View File
@@ -1,32 +1,80 @@
#include "auth_errors.hpp"
#include "repo/session_repository.hpp"
#include "repo/user_repository.hpp" #include "repo/user_repository.hpp"
#include "security/password_hasher.hpp" #include "security/password_hasher.hpp"
#include "security/session_token_service.hpp"
#include "util/time_utils.hpp" #include "util/time_utils.hpp"
#include "util/validation.hpp" #include "util/validation.hpp"
#include <service/auto_service.hpp> #include <service/auto_service.hpp>
#include <stdexcept> #include <stdexcept>
AuthService::AuthService(UserRepository &users, PasswordHasher &password_hasher) AuthService::AuthService(UserRepository &users, SessionRepository &session,
PasswordHasher &password_hasher, SessionTokenService &token_service,
std::chrono::seconds session_ttl)
: users_(users) : users_(users)
, passwordHasher_(password_hasher) {} , sessions_(session)
, password_hasher_(password_hasher)
RegisterResult AuthService::register_user(const RegisterCommand &command) const { , token_service_(token_service)
const auto email = normalize_email(command.email); , session_ttl_(session_ttl) {}
// LoginResult AuthService::login(const LoginCommand &loginCommand) const { const auto email }
void AuthService::validateData(std::string_view email, std::string_view password) const {
if(!is_valid_email(email)) { if(!is_valid_email(email)) {
throw std::runtime_error("Invalid email"); throw ValidationError{ "Invalid Email" };
} }
if(!is_valid_password(command.password)) { if(!is_valid_password(password)) {
throw std::runtime_error("Invalid password"); throw ValidationError("Invalid password");
} }
if(users_.find_by_email(email).has_value()) { if(users_.find_by_email(email).has_value()) {
throw std::runtime_error("Email already exists"); throw EmailAlreadyExistsError("Email already exists");
}
}
LoginResult AuthService::login(const LoginCommand &command) const {
const auto email = normalize_email(command.email);
// validateData(email, command.password);
if(!is_valid_email(email) || command.password.empty()) {
throw ValidationError{ "Invalid login request" };
} }
const auto password_hash = passwordHasher_.hash(command.password); const auto user = users_.find_by_email(email);
const auto now = now_utc_iso8601(); if(!user.has_value()) {
throw InvalidCredentialsError("Invalid email or password");
}
if(!user->is_active) {
throw UnauthorizedError("User is disabled");
}
try {
if(!password_hasher_.verify(command.password, user->password_hash)) {
throw InvalidCredentialsError{ "Invalid email or password" };
}
} catch(...) {
throw InvalidCredentialsError{ "Invalid email or password" };
}
const auto token_pair = token_service_.generate();
const auto created_at = now_utc_iso8601();
const auto expires_at = expires_at_from_now(session_ttl_);
sessions_.create(user->id, token_pair.token_hash, created_at, expires_at);
return LoginResult{
.session_token = token_pair.raw_token,
};
}
RegisterResult AuthService::register_user(const RegisterCommand &command) const {
const auto email = normalize_email(command.email);
validateData(email, command.password);
const auto password_hash = password_hasher_.hash(command.password);
const auto now = now_utc_iso8601();
const auto user = users_.create(email, password_hash, now, now); const auto user = users_.create(email, password_hash, now, now);
+24 -4
View File
@@ -1,9 +1,21 @@
#pragma once #pragma once
#include <chrono>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
class UserRepository; class UserRepository;
class PasswordHasher; class PasswordHasher;
class SessionRepository;
class SessionTokenService;
struct LoginCommand {
std::string email;
std::string password;
};
struct LoginResult {
std::string session_token;
};
struct RegisterCommand { struct RegisterCommand {
std::string email; std::string email;
@@ -17,11 +29,19 @@ struct RegisterResult {
class AuthService { class AuthService {
public: public:
AuthService(UserRepository &users, PasswordHasher &password_hasher); AuthService(UserRepository &users, SessionRepository &session, PasswordHasher &password_hasher,
SessionTokenService &token_service, std::chrono::seconds session_ttl);
RegisterResult register_user(const RegisterCommand &command) const; RegisterResult register_user(const RegisterCommand &command) const;
LoginResult login(const LoginCommand &loginCommand) const;
private: private:
UserRepository &users_; void validateData(std::string_view email, std::string_view password) const;
PasswordHasher &passwordHasher_;
}; private:
UserRepository &users_;
SessionRepository &sessions_;
PasswordHasher &password_hasher_;
SessionTokenService &token_service_;
std::chrono::seconds session_ttl_;
};