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);
runner.run_file("migrations/001_init.sql");
UserRepository user_repository(db);
PasswordHasher password_hasher;
AuthService auth_service(user_repository, password_hasher);
UserRepository user_repository(db);
PasswordHasher password_hasher;
SessionRepository session(db);
SessionTokenService token_service;
AuthService auth_service(user_repository, session, password_hasher, token_service,
settings_.session_ttl);
HealthController health_controller;
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(
"/register",
[&auth_controller](const drogon::HttpRequestPtr &req,
+85 -24
View File
@@ -1,13 +1,80 @@
#include "http/controllers/auth_controller.hpp"
#include "service/auth_errors.hpp"
#include "service/auto_service.hpp"
#include <drogon/drogon.h>
#include <json/json.h>
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(
const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const {
@@ -24,7 +91,7 @@ void AuthController::register_user(
}
try {
const auto result = authService_.register_user(RegisterCommand{
const auto result = auto_servise_.register_user(RegisterCommand{
.email = (*json)["email"].asString(),
.password = (*json)["password"].asString(),
});
@@ -37,30 +104,24 @@ void AuthController::register_user(
responce->setStatusCode(drogon::k201Created);
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) {
Json::Value error;
error["error"]["message"] = ex.what();
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";
error["error"]["code"] = "internal_error";
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
response->setStatusCode(drogon::k500InternalServerError);
@@ -71,4 +132,4 @@ void AuthController::register_user(
bool AuthController::isValidAuthJson(const std::shared_ptr<Json::Value> &req) const {
return req != nullptr || req->isMember("email") || req->isMember("password") ||
(*req)["email"].isString() || (*req)["password"].isString();
}
}
+7 -3
View File
@@ -12,9 +12,13 @@ public:
void register_user(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const;
private:
bool isValidAuthJson(const std::shared_ptr<Json::Value> &req) const;
void login(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const;
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 "security/password_hasher.hpp"
#include "security/session_token_service.hpp"
#include "util/time_utils.hpp"
#include "util/validation.hpp"
#include <service/auto_service.hpp>
#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)
, passwordHasher_(password_hasher) {}
RegisterResult AuthService::register_user(const RegisterCommand &command) const {
const auto email = normalize_email(command.email);
, sessions_(session)
, password_hasher_(password_hasher)
, token_service_(token_service)
, 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)) {
throw std::runtime_error("Invalid email");
throw ValidationError{ "Invalid Email" };
}
if(!is_valid_password(command.password)) {
throw std::runtime_error("Invalid password");
if(!is_valid_password(password)) {
throw ValidationError("Invalid password");
}
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 now = now_utc_iso8601();
const auto user = users_.find_by_email(email);
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);
+24 -4
View File
@@ -1,9 +1,21 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <string>
class UserRepository;
class PasswordHasher;
class SessionRepository;
class SessionTokenService;
struct LoginCommand {
std::string email;
std::string password;
};
struct LoginResult {
std::string session_token;
};
struct RegisterCommand {
std::string email;
@@ -17,11 +29,19 @@ struct RegisterResult {
class AuthService {
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;
LoginResult login(const LoginCommand &loginCommand) const;
private:
UserRepository &users_;
PasswordHasher &passwordHasher_;
};
void validateData(std::string_view email, std::string_view password) const;
private:
UserRepository &users_;
SessionRepository &sessions_;
PasswordHasher &password_hasher_;
SessionTokenService &token_service_;
std::chrono::seconds session_ttl_;
};