#pragma once #include "rublon/utils.hpp" #include #include #include #include #include namespace rublon::method { class PasscodeBasedAuth : public AuthenticationStep { protected: const char * uri; const char * confirmField; static constexpr const char * confirmCodeEndpoint = "/api/transaction/confirmCode"; static constexpr const char * confirmSecuritySSHEndpoint = "/api/transaction/confirmSecurityKeySSH"; static constexpr const char * fieldVericode = "vericode"; static constexpr const char * fieldOtp = "otp"; static constexpr auto _bypassCodeLength = 9; const char * userMessage{nullptr}; const uint_fast8_t length; const bool onlyDigits; int _prompts; constexpr static bool isdigit(char ch) { return std::isdigit(static_cast< unsigned char >(ch)); } bool digitsOnly(std::string_view userinput) const { return std::all_of(userinput.cbegin(), userinput.cend(), isdigit); } bool hasValidLength(std::string_view userInput) const { if(userInput.size() == length || userInput.size() == _bypassCodeLength) { log(LogLevel::Debug, "User input size %d is correct", userInput.size()); return true; } else { log(LogLevel::Warning, "User input size %d is different than %d", userInput.size(), length); return false; } } bool hasValidCharacters(std::string_view userInput) const { if(onlyDigits ? digitsOnly(userInput) : true) { log(LogLevel::Debug, "User input contains valid characters"); return true; } else { log(LogLevel::Warning, "User input contains characters different than digits"); return false; } } template < typename PamInfo_t = LinuxPam > tl::expected< std::reference_wrapper< Document >, Error > readPasscode(Document & body, const PamInfo_t & pam) const { auto & alloc = body.GetAllocator(); auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage); if(hasValidLength(vericode) and hasValidCharacters(vericode)) { Value confirmFieldValue(confirmField, alloc); body.AddMember(confirmFieldValue, Value{vericode.c_str(), alloc}, alloc); if(token.size()) { this->addAccessToken(body, token); } return body; } return tl::unexpected{Error{WerificationError{WerificationError::BadInput}}}; } template < typename PamInfo_t = LinuxPam > tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & /*coreResponse*/, const PamInfo_t & pam) const { pam.print("The passcode has been validated"); return AuthenticationStatus{AuthenticationStatus::Action::Confirmed}; } tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation( std::shared_ptr< WebSocketSingleShotEventListener > eventListener) const { log(LogLevel::Info, "Listening to confirmation event in PasscodeBasedAuth"); return eventListener->waitForEvent(); } template < typename PamInfo_t = LinuxPam > tl::expected< AuthenticationStatus, Error > errorHandler(Error error, const PamInfo_t & pam, int promptLeft) const { if(promptLeft && error.is< WerificationError >()) { switch(error.get< WerificationError >().errorClass) { case WerificationError::ErrorClass::PasscodeException: pam.print(R"(Incorrect passcode. Try again)"); break; case WerificationError::ErrorClass::BadInput: pam.print("The passcode has an incorrect number of digits or contains invalid characters. Try again"); break; } } return tl::unexpected{error}; } public: const char * _name; std::string token; enum class Endpoint { ConfirmCode, SecurityKeySSH }; PasscodeBasedAuth(std::string systemToken, std::string tid, std::string token, const char * _name, const char * userMessage, uint_fast8_t length, bool numbersOnly, Endpoint endpoint, int prompts) : AuthenticationStep(std::move(systemToken), std::move(tid)), uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint}, confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp}, userMessage{userMessage}, length{length}, onlyDigits{numbersOnly}, _prompts{prompts}, _name{_name}, token{std::move(token)} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { RapidJSONPMRStackAlloc< 2048 > alloc{}; Document body{rapidjson::kObjectType, &alloc}; std::shared_ptr< WebSocketSingleShotEventListener > eventListener = coreHandler.listen(); int prompts = _prompts; const auto requestAuthorization = [&](const auto & body) { return coreHandler.request(alloc, uri, body); }; const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); }; const auto waitForCoreToConfirm = [&](const auto &) { return waitForCoreConfirmation(eventListener); }; const auto handleError = [&](const auto error) { return errorHandler(error, pam, prompts); }; this->addSystemToken(body); this->addTid(body); return [&]() { while(true) { prompts--; auto access = readPasscode(body, pam) // .and_then(requestAuthorization) .and_then(checkCodeValidity) .and_then(waitForCoreToConfirm) .or_else(handleError); if(not access && prompts) { continue; } else { return access; } }; }(); } }; } // namespace rublon::method