* Add phone call authentication method * Remove dynamic mem allocation from error handler * Add more error handling code * Move error handling to different file * Remove Socket IO dependency * cleanup in websocket code * Add rapidjson as cmake dependency * Added Dockerfiles as primary build system for packages * Changed policy in CMakeList to work with lower version of CMake * Fix opensuse builds * Link filesystem library in gcc 8.5 or older
174 lines
6.8 KiB
C++
174 lines
6.8 KiB
C++
#pragma once
|
|
|
|
#include <tl/expected.hpp>
|
|
|
|
#include <rublon/authentication_step_interface.hpp>
|
|
#include <rublon/bits.hpp>
|
|
#include <rublon/pam_action.hpp>
|
|
#include <rublon/utils.hpp>
|
|
#include <rublon/websockets.hpp>
|
|
|
|
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
|