algotradelib 0.0.0.9000
C++ header-only library for algorithmic trading
Loading...
Searching...
No Matches
oauth.hpp
1#pragma once
2
8#define ATL_TRANSPORT_LAYER_PRESENT 1
9
10#include <boost/json.hpp>
11
12#include <charconv>
13#include <chrono>
14#include <cstdint>
15#include <optional>
16#include <stdexcept>
17#include <string>
18#include <string_view>
19
20namespace algotradelib {
21
22namespace json = boost::json;
23
24namespace network {
25
35template <typename BrokerType>
37public:
39 static constexpr std::string_view grant_type = "client_credentials";
40
46 void from_env() { static_cast<BrokerType*>(this)->from_env_impl(); }
47
54 void from_json(const std::string& filename) {
55 static_cast<BrokerType*>(this)->from_json_impl(filename);
56 }
57
62 const std::string& key() const noexcept { return key_; }
63
68 const std::string& secret() const noexcept { return secret_; }
69
75 std::string to_jsonbody() const {
76 json::object value;
77 value["grant_type"] = grant_type;
78 value[BrokerType::key_field()] = key_;
79 value[BrokerType::secret_field()] = secret_;
80 return json::serialize(value);
81 }
82
87 std::string to_form_urlencoded_body() const {
88 return "grant_type=client_credentials&client_id=" + form_encode(key_) +
89 "&client_secret=" + form_encode(secret_);
90 }
91
92protected:
94 std::string key_;
96 std::string secret_;
97
98private:
99 // Percent-encodes one form value using the RFC 3986 unreserved character set.
100 //
101 // Encoding reserved characters such as `&`, `=`, `+`, and `%` prevents a
102 // credential value from changing the surrounding form-field structure.
103 static std::string form_encode(std::string_view value) {
104 static constexpr char hex[] = "0123456789ABCDEF";
105 std::string result;
106 result.reserve(value.size());
107 for (const unsigned char ch : value) {
108 const bool unreserved = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
109 (ch >= '0' && ch <= '9') || ch == '-' || ch == '.' || ch == '_' || ch == '~';
110 if (unreserved) {
111 result.push_back(static_cast<char>(ch));
112 } else {
113 result.push_back('%');
114 result.push_back(hex[ch >> 4]);
115 result.push_back(hex[ch & 0x0F]);
116 }
117 }
118 return result;
119 }
120};
121
130template <typename BrokerType>
131struct AuthBody {
133 std::string access_token_;
135 std::chrono::system_clock::time_point expires_at_{};
136
147 void from_json(const json::object& document) {
148 const std::string_view field = BrokerType::token_field();
149 const auto token = document.if_contains(field);
150 if (!token || !token->is_string()) {
151 throw std::runtime_error("token response has no " + std::string(field) + " string");
152 }
153 access_token_ = token->as_string().c_str();
154
155 expires_at_ = std::chrono::system_clock::time_point{};
156 if (const auto value = document.if_contains("expires_in")) {
157 std::optional<std::int64_t> expires_in;
158 if (value->is_int64()) {
159 expires_in = value->as_int64();
160 } else if (value->is_uint64()) {
161 expires_in = static_cast<std::int64_t>(value->as_uint64());
162 } else if (value->is_string()) {
163 expires_in = parse_seconds(value->as_string());
164 }
165 if (expires_in) {
166 expires_at_ = std::chrono::system_clock::now() + std::chrono::seconds(*expires_in);
167 }
168 }
169 static_cast<BrokerType*>(this)->from_json_impl(document);
170 if (expires_at_ == std::chrono::system_clock::time_point{}) {
171 throw std::runtime_error(
172 "token response states no expiry, neither expires_in nor a broker-specific absolute one"
173 );
174 }
175 }
176
182 bool valid(std::chrono::seconds margin = std::chrono::minutes(5)) const noexcept {
183 return !access_token_.empty() && std::chrono::system_clock::now() + margin < expires_at_;
184 }
185
186private:
187 // Reads a whole number of seconds, or nothing when the text is not one. A malformed `expires_in`
188 // leaves `expires_at_` unset, so it reaches the no-expiry refusal rather than throwing an
189 // exception that names neither the broker nor the field.
190 static std::optional<std::int64_t> parse_seconds(std::string_view text) {
191 std::int64_t seconds{0};
192 const auto parsed = std::from_chars(text.data(), text.data() + text.size(), seconds);
193 if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) {
194 return std::nullopt;
195 }
196 return seconds;
197 }
198};
199
200} // namespace network
201} // namespace algotradelib
OAuth client credentials (an app key and secret), the base of each broker's credentials class.
Definition oauth.hpp:36
void from_env()
Loads the credentials from the broker's environment variables.
Definition oauth.hpp:46
std::string to_form_urlencoded_body() const
Builds a form-encoded token-request body (grant_type, client_id, client_secret).
Definition oauth.hpp:87
const std::string & key() const noexcept
Returns the app key (client id).
Definition oauth.hpp:62
static constexpr std::string_view grant_type
The OAuth grant type sent in a token request.
Definition oauth.hpp:39
std::string key_
The app key (client id).
Definition oauth.hpp:94
std::string secret_
The app secret.
Definition oauth.hpp:96
void from_json(const std::string &filename)
Loads the credentials from a JSON file, in the broker's format.
Definition oauth.hpp:54
std::string to_jsonbody() const
Builds a JSON token-request body (grant type, key and secret under the broker's field names).
Definition oauth.hpp:75
const std::string & secret() const noexcept
Returns the app secret.
Definition oauth.hpp:68
HTTP and WebSocket transport, OAuth tokens, and request pacing.
The library: broker-neutral types (core), and the modules below.
An OAuth access token and its expiry, the base of each broker's token-response class.
Definition oauth.hpp:131
bool valid(std::chrono::seconds margin=std::chrono::minutes(5)) const noexcept
Tests whether the token is set and stays valid for at least a margin.
Definition oauth.hpp:182
std::chrono::system_clock::time_point expires_at_
When the token expires.
Definition oauth.hpp:135
void from_json(const json::object &document)
Reads a token response.
Definition oauth.hpp:147
std::string access_token_
The bearer token; do not log it.
Definition oauth.hpp:133