rusty_ems/auth/
traits.rs

1//! Common traits for authentication across all exchange implementations
2
3use crate::error::{EMSError, Result};
4use async_trait::async_trait;
5use reqwest::header::HeaderMap;
6use rusty_common::SmartString;
7use std::time::Duration;
8
9/// Authentication context for a request
10#[derive(Debug, Clone)]
11pub struct AuthenticationContext {
12    /// HTTP method (GET, POST, PUT, DELETE)
13    pub method: SmartString,
14    /// API path (e.g., "/api/v3/order")
15    pub path: SmartString,
16    /// Query parameters
17    pub query_params: Option<Vec<(SmartString, SmartString)>>,
18    /// Request body (if any)
19    pub body: Option<SmartString>,
20    /// Request timestamp (optional, will use current time if None)
21    pub timestamp: Option<u64>,
22    /// Additional context-specific data
23    pub metadata: Option<SmartString>,
24}
25
26impl AuthenticationContext {
27    /// Create a new authentication context
28    pub fn new(method: impl Into<SmartString>, path: impl Into<SmartString>) -> Self {
29        Self {
30            method: method.into(),
31            path: path.into(),
32            query_params: None,
33            body: None,
34            timestamp: None,
35            metadata: None,
36        }
37    }
38
39    /// Set query parameters
40    #[must_use]
41    pub fn with_query_params(mut self, params: Vec<(SmartString, SmartString)>) -> Self {
42        self.query_params = Some(params);
43        self
44    }
45
46    /// Set request body
47    pub fn with_body(mut self, body: impl Into<SmartString>) -> Self {
48        self.body = Some(body.into());
49        self
50    }
51
52    /// Set timestamp
53    #[must_use]
54    pub const fn with_timestamp(mut self, timestamp: u64) -> Self {
55        self.timestamp = Some(timestamp);
56        self
57    }
58
59    /// Set metadata
60    pub fn with_metadata(mut self, metadata: impl Into<SmartString>) -> Self {
61        self.metadata = Some(metadata.into());
62        self
63    }
64}
65
66/// Result of authentication operations
67#[derive(Debug, Clone)]
68pub struct AuthenticationResult {
69    /// HTTP headers for the request
70    pub headers: HeaderMap,
71    /// Signed query string (if applicable)
72    pub signed_query: Option<SmartString>,
73    /// WebSocket authentication message (if applicable)
74    pub ws_auth_message: Option<SmartString>,
75    /// Authentication validity duration
76    pub validity_duration: Option<Duration>,
77}
78
79impl AuthenticationResult {
80    /// Create a new authentication result with headers
81    #[must_use]
82    pub const fn new(headers: HeaderMap) -> Self {
83        Self {
84            headers,
85            signed_query: None,
86            ws_auth_message: None,
87            validity_duration: None,
88        }
89    }
90
91    /// Set signed query string
92    pub fn with_signed_query(mut self, query: impl Into<SmartString>) -> Self {
93        self.signed_query = Some(query.into());
94        self
95    }
96
97    /// Set WebSocket authentication message
98    pub fn with_ws_auth_message(mut self, message: impl Into<SmartString>) -> Self {
99        self.ws_auth_message = Some(message.into());
100        self
101    }
102
103    /// Set validity duration
104    #[must_use]
105    pub const fn with_validity_duration(mut self, duration: Duration) -> Self {
106        self.validity_duration = Some(duration);
107        self
108    }
109}
110
111/// Unified authentication manager trait for all exchanges
112#[async_trait]
113pub trait AuthenticationManager: Send + Sync {
114    /// Generate authentication for a REST API request
115    async fn authenticate_rest_request(
116        &self,
117        context: &AuthenticationContext,
118    ) -> Result<AuthenticationResult>;
119
120    /// Generate authentication for a WebSocket connection
121    async fn authenticate_websocket(&self) -> Result<AuthenticationResult>;
122
123    /// Generate WebSocket trading authentication (if supported)
124    async fn authenticate_websocket_trading(
125        &self,
126        timestamp: Option<u64>,
127    ) -> Result<AuthenticationResult> {
128        let _ = timestamp;
129        Err(EMSError::not_supported(
130            self.exchange_name(),
131            "WebSocket trading authentication",
132        ))
133    }
134
135    /// Check if authentication is still valid
136    fn is_authentication_valid(&self) -> bool;
137
138    /// Get the time until authentication expires (if applicable)
139    fn time_until_expiration(&self) -> Option<Duration>;
140
141    /// Refresh authentication if needed (for JWT-based exchanges)
142    async fn refresh_authentication(&self) -> Result<()> {
143        Ok(()) // Default: no refresh needed
144    }
145
146    /// Get the exchange name
147    fn exchange_name(&self) -> &str;
148
149    /// Get the API key
150    fn api_key(&self) -> &str;
151
152    /// Check if this authentication supports WebSocket trading
153    fn supports_websocket_trading(&self) -> bool {
154        false
155    }
156
157    /// Check if this authentication requires periodic refresh
158    fn requires_refresh(&self) -> bool {
159        false
160    }
161
162    /// Get refresh interval (if applicable)
163    fn refresh_interval(&self) -> Option<Duration> {
164        None
165    }
166}
167
168/// Trait for authentication adapters that wrap exchange-specific auth implementations
169#[async_trait]
170pub trait AuthenticationAdapter: AuthenticationManager {
171    /// Get the underlying exchange-specific authentication implementation
172    fn underlying_auth(&self) -> &dyn std::any::Any;
173}
174
175/// WebSocket authentication message types
176#[derive(Debug, Clone)]
177pub enum WebSocketAuthMessage {
178    /// Simple authentication message (JSON)
179    Simple(SmartString),
180    /// JWT-based authentication
181    Jwt(SmartString),
182    /// Custom authentication with metadata
183    Custom {
184        /// The authentication message
185        message: SmartString,
186        /// Additional metadata for the authentication
187        metadata: SmartString,
188    },
189}
190
191/// Authentication method types supported by exchanges
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum AuthenticationMethod {
194    /// HMAC-SHA256 signature
195    HmacSha256,
196    /// Ed25519 signature (Binance advanced)
197    Ed25519,
198    /// ECDSA signature (Coinbase)
199    Ecdsa,
200    /// JWT with HMAC (Korean exchanges)
201    JwtHmac,
202    /// Custom authentication method
203    Custom,
204}
205
206/// Authentication requirements for different operation types
207#[derive(Debug, Clone)]
208pub struct AuthenticationRequirements {
209    /// Whether REST API authentication is required
210    pub rest_required: bool,
211    /// Whether WebSocket authentication is required
212    pub websocket_required: bool,
213    /// Whether WebSocket trading authentication is required
214    pub websocket_trading_required: bool,
215    /// Authentication method used
216    pub method: AuthenticationMethod,
217    /// Whether authentication has expiration
218    pub has_expiration: bool,
219    /// Default validity duration
220    pub default_validity: Option<Duration>,
221}
222
223impl AuthenticationRequirements {
224    /// Create requirements for HMAC-based authentication
225    #[must_use]
226    pub const fn hmac_based() -> Self {
227        Self {
228            rest_required: true,
229            websocket_required: false,
230            websocket_trading_required: false,
231            method: AuthenticationMethod::HmacSha256,
232            has_expiration: false,
233            default_validity: None,
234        }
235    }
236
237    /// Create requirements for JWT-based authentication
238    #[must_use]
239    pub const fn jwt_based() -> Self {
240        Self {
241            rest_required: true,
242            websocket_required: true,
243            websocket_trading_required: false,
244            method: AuthenticationMethod::JwtHmac,
245            has_expiration: true,
246            default_validity: Some(Duration::from_secs(3600)), // 1 hour
247        }
248    }
249
250    /// Create requirements for Ed25519-based authentication
251    #[must_use]
252    pub const fn ed25519_based() -> Self {
253        Self {
254            rest_required: true,
255            websocket_required: true,
256            websocket_trading_required: true,
257            method: AuthenticationMethod::Ed25519,
258            has_expiration: false,
259            default_validity: None,
260        }
261    }
262}