Add some functionality
new handler for Login add errors
This commit is contained in:
@@ -43,11 +43,23 @@ int Application::run() const {
|
|||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -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,29 +104,23 @@ void AuthController::register_user(
|
|||||||
responce->setStatusCode(drogon::k201Created);
|
responce->setStatusCode(drogon::k201Created);
|
||||||
|
|
||||||
callback(responce);
|
callback(responce);
|
||||||
} catch(const std::exception &ex) {
|
} catch(const EmailAlreadyExistsError &ex) {
|
||||||
Json::Value error;
|
Json::Value error;
|
||||||
error["error"]["message"] = ex.what();
|
error["error"]["message"] = ex.what();
|
||||||
|
|
||||||
const std::string message = ex.what();
|
|
||||||
|
|
||||||
if(message == "Email already exists") {
|
|
||||||
error["error"]["code"] = "email_already_exists";
|
error["error"]["code"] = "email_already_exists";
|
||||||
|
|
||||||
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
|
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
|
||||||
response->setStatusCode(drogon::k409Conflict);
|
response->setStatusCode(drogon::k409Conflict);
|
||||||
callback(response);
|
} catch(const ValidationError &ex) {
|
||||||
return;
|
Json::Value error;
|
||||||
}
|
error["error"]["message"] = ex.what();
|
||||||
|
|
||||||
if(message == "Invalid email" || message == "Invalid password") {
|
|
||||||
error["error"]["code"] = "validation_error";
|
error["error"]["code"] = "validation_error";
|
||||||
|
|
||||||
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
|
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
|
||||||
callback(response);
|
callback(response);
|
||||||
return;
|
} catch(const std::exception &ex) {
|
||||||
}
|
Json::Value error;
|
||||||
|
error["error"]["message"] = ex.what();
|
||||||
error["error"]["code"] = "internal_error";
|
error["error"]["code"] = "internal_error";
|
||||||
|
|
||||||
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
|
auto response = drogon::HttpResponse::newHttpJsonResponse(error);
|
||||||
|
|||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
@@ -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,31 +1,79 @@
|
|||||||
|
#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);
|
||||||
|
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 now = now_utc_iso8601();
|
||||||
|
|
||||||
const auto user = users_.create(email, password_hash, now, now);
|
const auto user = users_.create(email, password_hash, now, now);
|
||||||
|
|||||||
@@ -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:
|
||||||
|
void validateData(std::string_view email, std::string_view password) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UserRepository &users_;
|
UserRepository &users_;
|
||||||
PasswordHasher &passwordHasher_;
|
SessionRepository &sessions_;
|
||||||
|
PasswordHasher &password_hasher_;
|
||||||
|
SessionTokenService &token_service_;
|
||||||
|
std::chrono::seconds session_ttl_;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user