203 lines
7.9 KiB
C++
Executable File
203 lines
7.9 KiB
C++
Executable File
#pragma once
|
|
|
|
#include "rublon/error.hpp"
|
|
#include "rublon/static_string.hpp"
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <rublon/configuration.hpp>
|
|
#include <rublon/core_handler_interface.hpp>
|
|
#include <rublon/curl.hpp>
|
|
#include <rublon/json.hpp>
|
|
#include <rublon/pam_action.hpp>
|
|
#include <rublon/sign.hpp>
|
|
#include <rublon/utils.hpp>
|
|
#include <rublon/websockets.hpp>
|
|
|
|
#include <string_view>
|
|
#include <tl/expected.hpp>
|
|
|
|
namespace rublon {
|
|
|
|
template < typename HttpHandler = CURL >
|
|
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
|
|
std::reference_wrapper< const Configuration > _config;
|
|
mutable std::unique_ptr< WebSocket > _ws; /// TODO try to remove mutable modyfier
|
|
HttpHandler http{};
|
|
|
|
const Configuration & config() const noexcept {
|
|
return _config.get();
|
|
}
|
|
|
|
void signRequest(Request & request) const {
|
|
request.headers["X-Rublon-Signature"] =
|
|
std::pmr::string{signData(request.body, config().secretKey).c_str(), request.headers.get_allocator()};
|
|
}
|
|
|
|
bool hasSignature(const Response & response) const {
|
|
if(not response.headers.count("x-rublon-signature")) {
|
|
log(LogLevel::Error, "No x-rublon-signature");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// bool hasRateLimit(const Response & response)const {
|
|
// if(not response.headers.count("x-ratelimit-remaining")) {
|
|
// log(LogLevel::Error, "No x-ratelimit-remaining");
|
|
// return false;
|
|
// }
|
|
// return true;
|
|
// }
|
|
|
|
// bool rateIsAcceptable(const Response &response)const{
|
|
//// todo validate rate type and number
|
|
// }
|
|
|
|
bool signatureIsNatValid(const Response & response) const {
|
|
const auto & xRubResp = response.headers.at("x-rublon-signature");
|
|
const auto & sign = signData(response.body, config().secretKey);
|
|
const bool signatureMatch = xRubResp == sign.data();
|
|
if(not signatureMatch)
|
|
log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data());
|
|
return not signatureMatch;
|
|
}
|
|
|
|
bool hasException(const Document & coreResponse) const {
|
|
log(LogLevel::Trace, "Checking error status in core response");
|
|
using namespace std::string_view_literals;
|
|
return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv;
|
|
}
|
|
|
|
bool isUnHealthy(const Document & coreResponse) const {
|
|
log(LogLevel::Trace, "Checking errors in core response");
|
|
return coreResponse.HasParseError();
|
|
}
|
|
|
|
public:
|
|
CoreHandler() = delete;
|
|
CoreHandler(const Configuration & baseconfig) : _config{baseconfig}, _ws{std::make_unique< WebSocket >(_config)}, http{config()} {
|
|
log(LogLevel::Debug, "Core Handler apiServer: %s", config().apiServer.c_str());
|
|
}
|
|
CoreHandler(const CoreHandler &) = delete;
|
|
CoreHandler(CoreHandler &&) noexcept = delete;
|
|
|
|
CoreHandler & operator=(const CoreHandler &) = delete;
|
|
CoreHandler & operator=(CoreHandler &&) = delete;
|
|
|
|
~CoreHandler() noexcept = default;
|
|
|
|
tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const {
|
|
if(hasSignature(response) and signatureIsNatValid(response)) {
|
|
log(LogLevel::Error, "rublon core response is not signed properly");
|
|
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// tl::expected< std::reference_wrapper< const Response >, Error > validateRate(const Response & response) const {
|
|
// if(hasSignature(response) and signatureIsNatValid(response)) {
|
|
// log(LogLevel::Error, "rublon core response is not signed properly");
|
|
// return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
|
// }
|
|
// return response;
|
|
// }
|
|
|
|
tl::expected< Document, Error > validateResponse(RapidJSONPMRAlloc & alloc, const Response & response) const {
|
|
tl::expected< Document, Error > resp{&alloc};
|
|
resp->Parse(response.body.c_str());
|
|
|
|
log(LogLevel::Debug, "Starting Core Response validation");
|
|
|
|
if(isUnHealthy(*resp)) {
|
|
log(LogLevel::Error, "Rublon Core responded with broken data");
|
|
return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}};
|
|
} else if(hasException(*resp)) {
|
|
const auto * exception = JSONPointer{"/result/exception", &alloc}.Get(*resp);
|
|
const auto * message = JSONPointer{"/result/errorMessage", &alloc}.Get(*resp);
|
|
|
|
log(LogLevel::Error,
|
|
"Rublon Core responded with '%s' exception",
|
|
exception != nullptr ? exception->GetString() : "not specyfied");
|
|
|
|
log(LogLevel::Error, "%s", message != nullptr ? message->GetString() : "");
|
|
|
|
return handleCoreException(exception->GetString());
|
|
} else if(not hasSignature(response)) {
|
|
// additional check for mallformed responses (A invalid response, without any x-rublon-signature will stop at this check)
|
|
return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}};
|
|
}
|
|
|
|
log(LogLevel::Debug, "Core Response validated OK");
|
|
return resp;
|
|
}
|
|
|
|
tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const {
|
|
// can happen only during check application step
|
|
if(auto error = RublonCheckApplicationException::fromString(exceptionString); error.has_value())
|
|
return tl::unexpected{Error{error.value()}};
|
|
|
|
// auth problems likely bad configuration in rublon admin console
|
|
if(auto error = RublonAuthenticationInterrupt::fromString(exceptionString); error.has_value())
|
|
return tl::unexpected{Error{error.value()}};
|
|
|
|
// verification error wrong passcode etc.
|
|
if(auto error = WerificationError::fromString(exceptionString); error.has_value())
|
|
return tl::unexpected{Error{error.value()}};
|
|
|
|
// CoreHandlerError::TransactionAccessTokenExpiredException
|
|
|
|
// other exceptions, just "throw"
|
|
return tl::unexpected{Error{CoreHandlerError{CoreHandlerError::RublonCoreException}}};
|
|
}
|
|
|
|
tl::expected< Document, Error > handleError(const Error & error) const {
|
|
return tl::unexpected{Error{error}};
|
|
}
|
|
|
|
tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const {
|
|
memory::Monotonic_8k_Resource memoryResource;
|
|
|
|
const auto validateSignature = [&](const auto & arg) { return this->validateSignature(arg); };
|
|
const auto validateResponse = [&](const auto & arg) { return this->validateResponse(mr, arg); };
|
|
const auto handleError = [&](const auto & error) { return this->handleError(error); };
|
|
const auto pmrs = [&](const auto & txt) { return std::pmr::string{txt, &memoryResource}; };
|
|
|
|
Request request{&memoryResource};
|
|
Response response{&memoryResource};
|
|
|
|
request.headers["Content-Type"] = pmrs("application/json");
|
|
request.headers["Accept"] = pmrs("application/json");
|
|
|
|
stringifyTo(body, request.body);
|
|
|
|
signRequest(request);
|
|
std::pmr::string uri{&memoryResource};
|
|
uri.reserve(config().apiServer.size() + path.size() + 1);
|
|
uri += config().apiServer;
|
|
uri += path.data();
|
|
|
|
return http
|
|
.request(uri, request, response) //
|
|
.and_then(validateSignature)
|
|
.and_then(validateResponse)
|
|
.or_else(handleError);
|
|
}
|
|
|
|
bool createWSConnection(std::string_view tid) const {
|
|
if(not _ws) {
|
|
_ws.reset(new WebSocket(config()));
|
|
}
|
|
/// TODO connect can be separated from subscribtion on event
|
|
/// TODO status of attach is not checked
|
|
return _ws->AttachToCore(tid);
|
|
}
|
|
|
|
RublonEventData listen() const {
|
|
return _ws->listen();
|
|
}
|
|
};
|
|
|
|
} // namespace rublon
|