#include #include #include #include "utils.h" #include "authenticator.h" #include "logger.h" struct LoginFail { std::mutex mutex; std::atomic count; time_t lastfail; }; static std::map loginFails; Authenticator::Authenticator(UserDao &userDao) { this->userDao = &userDao; } // TODO: make failure counter configurable bool Authenticator::isBanned(std::string ip) { if(utils::hasKey(loginFails, ip)) { LoginFail &fl = loginFails[ip]; std::lock_guard lock(fl.mutex); return fl.count > 5 && (time(nullptr) - fl.lastfail) < 1200; } return false; } void Authenticator::incFailureCount(std::string ip) { LoginFail &fl = loginFails[ip]; fl.count += 1; fl.lastfail = time(nullptr); } std::vector Authenticator::pbkdf5(std::string password, const std::vector &salt) { unsigned char hash[32]; const EVP_MD *sha256 = EVP_sha256(); const unsigned char *rawsalt = reinterpret_cast(salt.data()); int ret = PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash); if(ret != 1) { Logger::error() << "Authenticator: pbkdf5: Failed to create hash"; return { }; } std::vector result; for(size_t i = 0; i < sizeof(hash); i++) { result.push_back(static_cast(hash[i])); } return result; } std::variant Authenticator::authenticate(std::string username, std::string password) { std::optional user = userDao->find(username); if(user) { if(user->enabled) { auto hashresult = pbkdf5(password, user.value().salt); if(hashresult.size() == 0) { return AuthenticationError::GeneralError; } // TODO: timing attack (even though practical relevancy questionable) if(hashresult == user.value().password) { return user.value(); } return AuthenticationError::PasswordNotMatch; } return AuthenticationError::UserDisabled; } return AuthenticationError::UserNotFound; } std::variant Authenticator::authenticate(std::string username, std::string password, std::string ip) { if(isBanned(ip)) { return AuthenticationError::BannedIP; } std::variant authresult = authenticate(username, password); if(std::holds_alternative(authresult)) { AuthenticationError error = std::get(authresult); if(error == AuthenticationError::PasswordNotMatch) { incFailureCount(ip); } return error; } return std::get(authresult); } std::vector Authenticator::hash(std::string password, const std::vector &salt) { return this->pbkdf5(password, salt); }