Skip to content

Commit a197127

Browse files
committed
Move auth_handler base class into its own implementation file
1 parent 1f2638b commit a197127

File tree

3 files changed

+206
-190
lines changed

3 files changed

+206
-190
lines changed

src/auth_handler.cpp

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#include "auth_handler.hpp"
2+
3+
#include "exception.hpp"
4+
#include "ga_strings.hpp"
5+
#include "logging.hpp"
6+
#include "session.hpp"
7+
#include "session_impl.hpp"
8+
9+
namespace ga {
10+
namespace sdk {
11+
namespace {
12+
// Server gives 3 attempts to get the twofactor code right before it's invalidated
13+
static const uint32_t TWO_FACTOR_ATTEMPTS = 3;
14+
15+
static bool is_twofactor_invalid_code_error(const std::string& msg)
16+
{
17+
return msg == "Invalid Two Factor Authentication Code";
18+
}
19+
} // namespace
20+
21+
//
22+
// Common auth handling
23+
//
24+
auth_handler::auth_handler(session& session, const std::string& action, std::shared_ptr<signer> signer)
25+
: m_session(session)
26+
, m_action(action)
27+
, m_attempts_remaining(TWO_FACTOR_ATTEMPTS)
28+
{
29+
try {
30+
init(action, signer, true);
31+
} catch (const std::exception& e) {
32+
set_error(e.what());
33+
}
34+
}
35+
36+
auth_handler::auth_handler(session& session, const std::string& action)
37+
: m_session(session)
38+
, m_action(action)
39+
, m_attempts_remaining(TWO_FACTOR_ATTEMPTS)
40+
{
41+
try {
42+
init(action, m_session.get_nonnull_impl()->get_signer(), false);
43+
} catch (const std::exception& e) {
44+
set_error(e.what());
45+
}
46+
}
47+
48+
auth_handler::~auth_handler() {}
49+
50+
void auth_handler::init(const std::string& action, std::shared_ptr<signer> signer, bool is_pre_login)
51+
{
52+
m_signer = signer;
53+
set_action(action);
54+
55+
if (!is_pre_login && !m_session.is_watch_only()) {
56+
m_methods = m_session.get_enabled_twofactor_methods();
57+
}
58+
m_state = m_methods.empty() ? state_type::make_call : state_type::request_code;
59+
}
60+
61+
void auth_handler::set_action(const std::string& action)
62+
{
63+
m_action = action;
64+
m_is_hw_action = m_signer && m_signer->is_hw_device()
65+
&& (action == "get_xpubs" || action == "sign_message" || action == "sign_tx"
66+
|| action == "get_receive_address" || action == "create_transaction" || action == "get_balance"
67+
|| action == "get_subaccounts" || action == "get_subaccount" || action == "get_transactions"
68+
|| action == "get_unspent_outputs" || action == "get_expired_deposits");
69+
}
70+
71+
void auth_handler::set_error(const std::string& error_message)
72+
{
73+
GDK_LOG_SEV(log_level::debug) << m_action << " call exception: " << error_message;
74+
m_state = state_type::error;
75+
m_error = error_message;
76+
}
77+
78+
void auth_handler::set_data()
79+
{
80+
m_twofactor_data
81+
= { { "action", m_action }, { "device", m_is_hw_action ? m_signer->get_hw_device() : nlohmann::json() } };
82+
}
83+
84+
void auth_handler::request_code(const std::string& method)
85+
{
86+
request_code_impl(method);
87+
m_attempts_remaining = TWO_FACTOR_ATTEMPTS;
88+
}
89+
90+
void auth_handler::request_code_impl(const std::string& method)
91+
{
92+
GDK_RUNTIME_ASSERT(m_state == state_type::request_code);
93+
94+
// For gauth request code is a no-op
95+
if (method != "gauth") {
96+
m_session.auth_handler_request_code(method, m_action, m_twofactor_data);
97+
}
98+
99+
m_method = method;
100+
m_state = state_type::resolve_code;
101+
}
102+
103+
void auth_handler::resolve_code(const std::string& code)
104+
{
105+
GDK_RUNTIME_ASSERT(m_state == state_type::resolve_code);
106+
m_code = code;
107+
m_state = state_type::make_call;
108+
}
109+
110+
void auth_handler::operator()()
111+
{
112+
GDK_RUNTIME_ASSERT(m_state == state_type::make_call);
113+
try {
114+
115+
if (m_code.empty() || m_method.empty()) {
116+
if (!m_twofactor_data.empty()) {
117+
// Remove any previous auth attempts
118+
m_twofactor_data.erase("method");
119+
m_twofactor_data.erase("code");
120+
}
121+
} else {
122+
m_twofactor_data["method"] = m_method;
123+
m_twofactor_data["code"] = m_code;
124+
}
125+
m_state = call_impl();
126+
m_attempts_remaining = TWO_FACTOR_ATTEMPTS;
127+
} catch (const autobahn::call_error& e) {
128+
auto details = get_error_details(e);
129+
if (is_twofactor_invalid_code_error(details.second)) {
130+
// The caller entered the wrong code
131+
// FIXME: Error if the methods time limit is up or we are rate limited
132+
if (m_method != "gauth" && --m_attempts_remaining == 0) {
133+
// No more attempts left, caller should try the action again
134+
set_error(res::id_invalid_twofactor_code);
135+
} else {
136+
// Caller should try entering the code again
137+
m_state = state_type::resolve_code;
138+
}
139+
} else {
140+
details = remap_ga_server_error(details);
141+
set_error(details.second.empty() ? e.what() : details.second);
142+
}
143+
} catch (const user_error& e) {
144+
// Just set the undecorated error string as it should be an id for a
145+
// translatable string resource, displayed as appropriate by the client.
146+
set_error(e.what());
147+
} catch (const std::exception& e) {
148+
set_error(m_action + std::string(" exception:") + e.what());
149+
}
150+
}
151+
152+
nlohmann::json auth_handler::get_status() const
153+
{
154+
GDK_RUNTIME_ASSERT(m_state == state_type::error || m_error.empty());
155+
156+
std::string status_str;
157+
nlohmann::json status;
158+
159+
switch (m_state) {
160+
case state_type::request_code:
161+
GDK_RUNTIME_ASSERT(!m_is_hw_action);
162+
163+
// Caller should ask the user to pick 2fa and request a code
164+
status_str = "request_code";
165+
status["methods"] = m_methods;
166+
break;
167+
case state_type::resolve_code:
168+
status_str = "resolve_code";
169+
if (m_is_hw_action) {
170+
// Caller must interact with the hardware and return
171+
// the returning data to us
172+
status["method"] = m_signer->get_hw_device().value("name", std::string());
173+
status["required_data"] = m_twofactor_data;
174+
} else {
175+
// Caller should resolve the code the user has entered
176+
status["method"] = m_method;
177+
if (m_method != "gauth") {
178+
status["attempts_remaining"] = m_attempts_remaining;
179+
}
180+
}
181+
break;
182+
case state_type::make_call:
183+
// Caller should make the call
184+
status_str = "call";
185+
break;
186+
case state_type::done:
187+
// Caller should destroy the call and continue
188+
status_str = "done";
189+
status["result"] = m_result;
190+
break;
191+
case state_type::error:
192+
// Caller should handle the error
193+
status_str = "error";
194+
status["error"] = m_error;
195+
break;
196+
}
197+
GDK_RUNTIME_ASSERT(!status_str.empty());
198+
status["status"] = status_str;
199+
status["action"] = m_action;
200+
status["device"] = m_is_hw_action ? m_signer->get_hw_device() : nlohmann::json();
201+
return status;
202+
}
203+
204+
} // namespace sdk
205+
} // namespace ga

0 commit comments

Comments
 (0)