use std::collections::HashMap;
use std::net::UdpSocket;
use totp_rs::{Algorithm, TOTP};
use crate::core::{ExchangeResult, ExchangeError};
#[derive(Clone)]
pub struct AngelOneAuth {
pub api_key: String,
pub client_code: String,
pub pin: String,
pub totp_secret: String,
pub jwt_token: Option<String>,
pub refresh_token: Option<String>,
pub feed_token: Option<String>,
}
impl AngelOneAuth {
pub fn new(
api_key: String,
client_code: String,
pin: String,
totp_secret: String,
) -> Self {
Self {
api_key,
client_code,
pin,
totp_secret,
jwt_token: None,
refresh_token: None,
feed_token: None,
}
}
pub fn generate_totp(&self) -> ExchangeResult<String> {
let totp = TOTP::new(
Algorithm::SHA1,
6, 1, 30, self.totp_secret.as_bytes().to_vec(),
).map_err(|e| ExchangeError::Auth(format!("Failed to create TOTP: {}", e)))?;
let code = totp.generate_current()
.map_err(|e| ExchangeError::Auth(format!("Failed to generate TOTP: {}", e)))?;
Ok(code)
}
pub fn set_tokens(&mut self, jwt: String, refresh: String, feed: String) {
self.jwt_token = Some(jwt);
self.refresh_token = Some(refresh);
self.feed_token = Some(feed);
}
pub fn clear_tokens(&mut self) {
self.jwt_token = None;
self.refresh_token = None;
self.feed_token = None;
}
pub fn jwt_token(&self) -> ExchangeResult<&str> {
self.jwt_token.as_deref()
.ok_or_else(|| ExchangeError::Auth("Not logged in - JWT token missing".to_string()))
}
pub fn refresh_token(&self) -> ExchangeResult<&str> {
self.refresh_token.as_deref()
.ok_or_else(|| ExchangeError::Auth("Refresh token missing".to_string()))
}
pub fn feed_token(&self) -> ExchangeResult<&str> {
self.feed_token.as_deref()
.ok_or_else(|| ExchangeError::Auth("Feed token missing".to_string()))
}
pub fn sign_headers(&self) -> ExchangeResult<HashMap<String, String>> {
let jwt = self.jwt_token()?;
let mut headers = HashMap::new();
headers.insert("Authorization".to_string(), format!("Bearer {}", jwt));
headers.insert("X-PrivateKey".to_string(), self.api_key.clone());
headers.insert("Content-Type".to_string(), "application/json".to_string());
let local_ip = UdpSocket::bind("0.0.0.0:0")
.and_then(|sock| {
sock.connect("8.8.8.8:80")?;
sock.local_addr()
})
.map(|addr| addr.ip().to_string())
.unwrap_or_else(|_| "127.0.0.1".to_string());
let mac_address = "02:00:00:00:00:00".to_string();
headers.insert("X-ClientLocalIP".to_string(), local_ip.clone());
headers.insert("X-ClientPublicIP".to_string(), local_ip);
headers.insert("X-MACAddress".to_string(), mac_address);
Ok(headers)
}
pub fn build_login_body(&self) -> ExchangeResult<serde_json::Value> {
let totp_code = self.generate_totp()?;
Ok(serde_json::json!({
"clientcode": self.client_code,
"password": self.pin,
"totp": totp_code
}))
}
pub fn build_refresh_body(&self) -> ExchangeResult<serde_json::Value> {
let refresh = self.refresh_token()?;
Ok(serde_json::json!({
"refreshToken": refresh
}))
}
pub fn build_logout_body(&self) -> serde_json::Value {
serde_json::json!({
"clientId": self.client_code
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_auth() {
let auth = AngelOneAuth::new(
"test_key".to_string(),
"A12345".to_string(),
"1234".to_string(),
"JBSWY3DPEHPK3PXP".to_string(), );
assert_eq!(auth.api_key, "test_key");
assert_eq!(auth.client_code, "A12345");
assert!(auth.jwt_token.is_none());
}
#[test]
fn test_set_tokens() {
let mut auth = AngelOneAuth::new(
"test_key".to_string(),
"A12345".to_string(),
"1234".to_string(),
"JBSWY3DPEHPK3PXP".to_string(),
);
auth.set_tokens(
"jwt123".to_string(),
"refresh123".to_string(),
"feed123".to_string(),
);
assert_eq!(auth.jwt_token().unwrap(), "jwt123");
assert_eq!(auth.refresh_token().unwrap(), "refresh123");
assert_eq!(auth.feed_token().unwrap(), "feed123");
}
#[test]
fn test_clear_tokens() {
let mut auth = AngelOneAuth::new(
"test_key".to_string(),
"A12345".to_string(),
"1234".to_string(),
"JBSWY3DPEHPK3PXP".to_string(),
);
auth.set_tokens("jwt".to_string(), "refresh".to_string(), "feed".to_string());
auth.clear_tokens();
assert!(auth.jwt_token.is_none());
assert!(auth.refresh_token.is_none());
assert!(auth.feed_token.is_none());
}
}