#pragma once #include #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 vericodeLength; 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() == vericodeLength || 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(), vericodeLength); 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; } } tl::expected< std::reference_wrapper< Document >, Error > readPasscode(Document & body, const Pam_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}}}; } tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & /*coreResponse*/, const Pam_t & pam) const { pam.print("The passcode has been validated"); return AuthenticationStatus{AuthenticationStatus::Action::Confirmed}; } template < typename Hander_t > tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation(const CoreHandlerInterface< Hander_t > & eventListener) const { log(LogLevel::Info, "Listening to confirmation event in PasscodeBasedAuth"); auto event = eventListener.listen(); if(event.status == transactionConfirmed ){ log(LogLevel::Debug, " transaction confirmed jupi"); return AuthenticationStatus{AuthenticationStatus::Action::Confirmed, std::string{event.accessToken.value().c_str()}}; } log(LogLevel::Debug, " transaction denied"); return AuthenticationStatus{AuthenticationStatus::Action::Denied}; } tl::expected< AuthenticationStatus, Error > errorHandler(Error error, const Pam_t & pam, int promptLeft) const { if(promptLeft && error.is< WerificationError >()) { switch(error.get< WerificationError >().errorClass) { case WerificationError::PasscodeException: pam.print(R"(Incorrect passcode. Try again)"); break; case WerificationError::BadInput: pam.print("The passcode has an incorrect number of digits or contains invalid characters. Try again"); break; case WerificationError::SecurityKeyException: pam.print("The entered code has an incorrect length or contains unacceptable characters. Try again"); break; case WerificationError::TooManyRequestsException: pam.print("Too Many Attempts. Try again after a minute"); break; } } return tl::unexpected{error}; } public: const char * _name; std::string token; // TODO StaticString enum class Endpoint { ConfirmCode, SecurityKeySSH }; PasscodeBasedAuth( // Session & session, std::string token, const char * _name, const char * userMessage, uint_fast8_t length, bool numbersOnly, Endpoint endpoint, int prompts) : AuthenticationStep(session), uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint}, confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp}, userMessage{userMessage}, vericodeLength{length}, onlyDigits{numbersOnly}, _prompts{prompts}, _name{_name}, token{std::move(token)} {} template < typename Hander_t > tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const Pam_t & pam) const { RapidJSONPMRStackAlloc< 2048 > alloc{}; Document body{rapidjson::kObjectType, &alloc}; 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(coreHandler); }; 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