Add some functionality
new handler for Login add errors
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user