rusty_ems/auth/
adapters.rs1use super::traits::{
4 AuthenticationAdapter, AuthenticationContext, AuthenticationManager, AuthenticationResult,
5};
6use crate::error::{EMSError, Result};
7use async_trait::async_trait;
8use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
9use rusty_common::SmartString;
10use rusty_common::auth::ExchangeAuth;
11use rusty_common::auth::exchanges::{
12 binance::BinanceAuth, bithumb::BithumbAuth, bybit::BybitAuth, coinbase::CoinbaseAuth,
13 upbit::UpbitAuth,
14};
15use std::any::Any;
16use std::time::Duration;
17
18pub struct BinanceAuthAdapter {
20 auth: BinanceAuth,
21 exchange_name: SmartString,
22}
23
24impl BinanceAuthAdapter {
25 #[must_use]
27 pub fn new(auth: BinanceAuth) -> Self {
28 Self {
29 auth,
30 exchange_name: "Binance".into(),
31 }
32 }
33}
34
35#[async_trait]
36impl AuthenticationManager for BinanceAuthAdapter {
37 async fn authenticate_rest_request(
38 &self,
39 context: &AuthenticationContext,
40 ) -> Result<AuthenticationResult> {
41 let params = context.query_params.as_ref().map(|p| {
43 p.iter()
44 .map(|(k, v)| (k.as_str(), v.as_str()))
45 .collect::<Vec<_>>()
46 });
47
48 let headers = self
49 .auth
50 .generate_headers(
51 &context.method,
52 &context.path,
53 params.as_deref(),
54 context.body.as_deref(),
55 )
56 .map_err(|e| EMSError::auth_exchange_failed("Binance", &e))?;
57
58 let mut header_map = HeaderMap::new();
60 for (key, value) in headers {
61 let header_name = HeaderName::from_bytes(key.as_bytes())
62 .map_err(|e| EMSError::auth_invalid_header_name(&e))?;
63 let header_value = HeaderValue::from_str(value.as_str())
64 .map_err(|e| EMSError::auth_invalid_header_value(&e))?;
65 header_map.insert(header_name, header_value);
66 }
67
68 let mut result = AuthenticationResult::new(header_map);
69
70 if let Some(params) = params
72 && let Ok(signed_query) = self.auth.generate_signed_query_string(Some(¶ms))
73 {
74 result = result.with_signed_query(signed_query);
75 }
76
77 Ok(result)
78 }
79
80 async fn authenticate_websocket(&self) -> Result<AuthenticationResult> {
81 Ok(AuthenticationResult::new(HeaderMap::new()))
84 }
85
86 async fn authenticate_websocket_trading(
87 &self,
88 timestamp: Option<u64>,
89 ) -> Result<AuthenticationResult> {
90 Err(EMSError::not_supported(
93 self.exchange_name.clone(),
94 "WebSocket trading authentication (requires Ed25519)",
95 ))
96 }
97
98 fn is_authentication_valid(&self) -> bool {
99 true }
101
102 fn time_until_expiration(&self) -> Option<Duration> {
103 None }
105
106 fn exchange_name(&self) -> &str {
107 &self.exchange_name
108 }
109
110 fn api_key(&self) -> &str {
111 self.auth.api_key()
112 }
113
114 fn supports_websocket_trading(&self) -> bool {
115 false
118 }
119}
120
121impl AuthenticationAdapter for BinanceAuthAdapter {
122 fn underlying_auth(&self) -> &dyn Any {
123 &self.auth
124 }
125}
126
127pub struct BybitAuthAdapter {
129 auth: BybitAuth,
130 exchange_name: SmartString,
131}
132
133impl BybitAuthAdapter {
134 #[must_use]
136 pub fn new(auth: BybitAuth) -> Self {
137 Self {
138 auth,
139 exchange_name: "Bybit".into(),
140 }
141 }
142}
143
144#[async_trait]
145impl AuthenticationManager for BybitAuthAdapter {
146 async fn authenticate_rest_request(
147 &self,
148 context: &AuthenticationContext,
149 ) -> Result<AuthenticationResult> {
150 let headers = self
151 .auth
152 .generate_headers(&context.method, &context.path, context.body.as_deref())
153 .map_err(|e| EMSError::auth_exchange_failed("Bybit", &e))?;
154
155 let mut header_map = HeaderMap::new();
157 for (key, value) in headers {
158 let header_name = HeaderName::from_bytes(key.as_bytes())
159 .map_err(|e| EMSError::auth_invalid_header_name(&e))?;
160 let header_value = HeaderValue::from_str(value.as_str())
161 .map_err(|e| EMSError::auth_invalid_header_value(&e))?;
162 header_map.insert(header_name, header_value);
163 }
164
165 Ok(AuthenticationResult::new(header_map))
166 }
167
168 async fn authenticate_websocket(&self) -> Result<AuthenticationResult> {
169 Ok(AuthenticationResult::new(HeaderMap::new()))
170 }
171
172 async fn authenticate_websocket_trading(
173 &self,
174 timestamp: Option<u64>,
175 ) -> Result<AuthenticationResult> {
176 let timestamp = timestamp.unwrap_or_else(BybitAuth::get_timestamp);
177 let header = self.auth.create_ws_trading_header(timestamp);
178
179 let ws_message = simd_json::json!({
180 "req_id": "auth",
181 "op": "auth",
182 "args": [header]
183 })
184 .to_string();
185
186 let result = AuthenticationResult::new(HeaderMap::new()).with_ws_auth_message(ws_message);
187
188 Ok(result)
189 }
190
191 fn is_authentication_valid(&self) -> bool {
192 true }
194
195 fn time_until_expiration(&self) -> Option<Duration> {
196 None
197 }
198
199 fn exchange_name(&self) -> &str {
200 &self.exchange_name
201 }
202
203 fn api_key(&self) -> &str {
204 self.auth.api_key()
205 }
206
207 fn supports_websocket_trading(&self) -> bool {
208 true
209 }
210}
211
212impl AuthenticationAdapter for BybitAuthAdapter {
213 fn underlying_auth(&self) -> &dyn Any {
214 &self.auth
215 }
216}
217
218pub struct CoinbaseAuthAdapter {
220 auth: CoinbaseAuth,
221 exchange_name: SmartString,
222}
223
224impl CoinbaseAuthAdapter {
225 #[must_use]
227 pub fn new(auth: CoinbaseAuth) -> Self {
228 Self {
229 auth,
230 exchange_name: "Coinbase".into(),
231 }
232 }
233}
234
235#[async_trait]
236impl AuthenticationManager for CoinbaseAuthAdapter {
237 async fn authenticate_rest_request(
238 &self,
239 context: &AuthenticationContext,
240 ) -> Result<AuthenticationResult> {
241 let headers = self
242 .auth
243 .generate_headers(&context.method, &context.path, context.body.as_deref())
244 .map_err(|e| EMSError::auth(format!("Coinbase auth failed: {e}")))?;
245
246 let mut header_map = HeaderMap::new();
248 for (key, value) in headers {
249 let header_name = HeaderName::from_bytes(key.as_bytes())
250 .map_err(|e| EMSError::auth_invalid_header_name(&e))?;
251 let header_value = HeaderValue::from_str(value.as_str())
252 .map_err(|e| EMSError::auth_invalid_header_value(&e))?;
253 header_map.insert(header_name, header_value);
254 }
255
256 Ok(AuthenticationResult::new(header_map))
257 }
258
259 async fn authenticate_websocket(&self) -> Result<AuthenticationResult> {
260 let jwt_token = self
261 .auth
262 .generate_jwt()
263 .map_err(|e| EMSError::auth(format!("Coinbase JWT generation failed: {e}")))?;
264
265 let result = AuthenticationResult::new(HeaderMap::new())
266 .with_ws_auth_message(jwt_token)
267 .with_validity_duration(Duration::from_secs(3600)); Ok(result)
270 }
271
272 fn is_authentication_valid(&self) -> bool {
273 true }
275
276 fn time_until_expiration(&self) -> Option<Duration> {
277 Some(Duration::from_secs(3600)) }
279
280 fn exchange_name(&self) -> &str {
281 &self.exchange_name
282 }
283
284 fn api_key(&self) -> &str {
285 self.auth.api_key()
286 }
287}
288
289impl AuthenticationAdapter for CoinbaseAuthAdapter {
290 fn underlying_auth(&self) -> &dyn Any {
291 &self.auth
292 }
293}
294
295pub struct UpbitAuthAdapter {
297 auth: UpbitAuth,
298 exchange_name: SmartString,
299}
300
301impl UpbitAuthAdapter {
302 #[must_use]
304 pub fn new(auth: UpbitAuth) -> Self {
305 Self {
306 auth,
307 exchange_name: "Upbit".into(),
308 }
309 }
310}
311
312#[async_trait]
313impl AuthenticationManager for UpbitAuthAdapter {
314 async fn authenticate_rest_request(
315 &self,
316 context: &AuthenticationContext,
317 ) -> Result<AuthenticationResult> {
318 let params = context.query_params.as_ref().and_then(|p| {
320 if p.is_empty() {
321 None
322 } else {
323 Some((p[0].0.as_str(), p[0].1.as_str()))
324 }
325 });
326
327 let headers = self
328 .auth
329 .generate_authentication_headers(&context.method, &context.path, params)
330 .map_err(|e| EMSError::auth(format!("Upbit auth failed: {e}")))?;
331
332 let mut header_map = HeaderMap::new();
334 for (key, value) in headers {
335 let header_name = HeaderName::from_bytes(key.as_bytes())
336 .map_err(|e| EMSError::auth_invalid_header_name(&e))?;
337 let header_value = HeaderValue::from_str(value.as_str())
338 .map_err(|e| EMSError::auth_invalid_header_value(&e))?;
339 header_map.insert(header_name, header_value);
340 }
341
342 Ok(AuthenticationResult::new(header_map))
343 }
344
345 async fn authenticate_websocket(&self) -> Result<AuthenticationResult> {
346 let ws_auth = self
347 .auth
348 .generate_websocket_authentication()
349 .map_err(|e| EMSError::auth(format!("Upbit WS auth failed: {e}")))?;
350
351 let result = AuthenticationResult::new(HeaderMap::new())
352 .with_ws_auth_message(ws_auth)
353 .with_validity_duration(Duration::from_secs(3600));
354
355 Ok(result)
356 }
357
358 fn is_authentication_valid(&self) -> bool {
359 true }
361
362 fn time_until_expiration(&self) -> Option<Duration> {
363 Some(Duration::from_secs(3600))
364 }
365
366 fn exchange_name(&self) -> &str {
367 &self.exchange_name
368 }
369
370 fn api_key(&self) -> &str {
371 self.auth.api_key()
372 }
373
374 fn requires_refresh(&self) -> bool {
375 true
376 }
377
378 fn refresh_interval(&self) -> Option<Duration> {
379 Some(Duration::from_secs(3000)) }
381}
382
383impl AuthenticationAdapter for UpbitAuthAdapter {
384 fn underlying_auth(&self) -> &dyn Any {
385 &self.auth
386 }
387}
388
389pub struct BithumbAuthAdapter {
391 auth: BithumbAuth,
392 exchange_name: SmartString,
393}
394
395impl BithumbAuthAdapter {
396 #[must_use]
398 pub fn new(auth: BithumbAuth) -> Self {
399 Self {
400 auth,
401 exchange_name: "Bithumb".into(),
402 }
403 }
404}
405
406#[async_trait]
407impl AuthenticationManager for BithumbAuthAdapter {
408 async fn authenticate_rest_request(
409 &self,
410 context: &AuthenticationContext,
411 ) -> Result<AuthenticationResult> {
412 let params = context.query_params.as_ref().map(|p| {
414 p.iter()
415 .map(|(k, v)| (k.as_str(), v.as_str()))
416 .collect::<Vec<_>>()
417 });
418
419 let headers = self
420 .auth
421 .generate_headers(&context.method, &context.path, params.as_deref())
422 .map_err(|e| EMSError::auth(format!("Bithumb auth failed: {e}")))?;
423
424 let mut header_map = HeaderMap::new();
426 for (key, value) in headers {
427 let header_name = HeaderName::from_bytes(key.as_bytes())
428 .map_err(|e| EMSError::auth_invalid_header_name(&e))?;
429 let header_value = HeaderValue::from_str(value.as_str())
430 .map_err(|e| EMSError::auth_invalid_header_value(&e))?;
431 header_map.insert(header_name, header_value);
432 }
433
434 Ok(AuthenticationResult::new(header_map))
435 }
436
437 async fn authenticate_websocket(&self) -> Result<AuthenticationResult> {
438 let ws_auth = self
440 .auth
441 .generate_ws_auth()
442 .map_err(|e| EMSError::auth(format!("Bithumb WS auth failed: {e}")))?;
443
444 let result = AuthenticationResult::new(HeaderMap::new())
445 .with_ws_auth_message(ws_auth)
446 .with_validity_duration(Duration::from_secs(3600));
447
448 Ok(result)
449 }
450
451 fn is_authentication_valid(&self) -> bool {
452 true }
454
455 fn time_until_expiration(&self) -> Option<Duration> {
456 Some(Duration::from_secs(3600))
457 }
458
459 fn exchange_name(&self) -> &str {
460 &self.exchange_name
461 }
462
463 fn api_key(&self) -> &str {
464 self.auth.api_key()
465 }
466
467 fn requires_refresh(&self) -> bool {
468 true
469 }
470
471 fn refresh_interval(&self) -> Option<Duration> {
472 Some(Duration::from_secs(3000)) }
474}
475
476impl AuthenticationAdapter for BithumbAuthAdapter {
477 fn underlying_auth(&self) -> &dyn Any {
478 &self.auth
479 }
480}