huawei_dongle_api/api/
auth.rs1use crate::{
4 auth::PasswordEncoder,
5 client::Client,
6 error::{Error, Result},
7 models::{auth::*, common::Response},
8};
9use tracing::{debug, trace};
10
11pub struct AuthApi<'a> {
13 client: &'a Client,
14}
15
16impl<'a> AuthApi<'a> {
17 pub fn new(client: &'a Client) -> Self {
18 Self { client }
19 }
20
21 pub async fn state_login(&self) -> Result<LoginState> {
24 debug!("Fetching login state");
25
26 let response = self.client.get("/api/user/state-login").await?;
27 let text = response.text().await?;
28
29 trace!("Login state response: {}", text);
30
31 let state: LoginState = serde_xml_rs::from_str(&text)
32 .map_err(|e| Error::generic(format!("Failed to parse login state: {e}")))?;
33
34 debug!(
35 "Login state: {} (password_type: {})",
36 if state.is_logged_in() {
37 "logged in"
38 } else {
39 "not logged in"
40 },
41 state.password_type
42 );
43
44 Ok(state)
45 }
46
47 pub async fn login(&self, username: &str, password: &str) -> Result<()> {
50 debug!("Attempting login for user: {}", username);
51
52 let login_state = self.state_login().await?;
53
54 if login_state.is_logged_in() {
55 debug!("User is already logged in");
56 return Ok(());
57 }
58
59 if login_state.is_locked() {
60 return Err(Error::session(format!(
61 "Account is locked. Wait time: {} seconds",
62 login_state.remain_wait_time
63 )));
64 }
65
66 let encoded_password = PasswordEncoder::encode_password(password, &login_state);
67
68 let request = LoginRequest::new(
69 username.to_string(),
70 encoded_password,
71 login_state.password_type.clone(),
72 );
73
74 let xml = serde_xml_rs::to_string(&request)
75 .map_err(|e| Error::generic(format!("Failed to serialize login request: {e}")))?;
76
77 trace!("Login request XML: {}", xml);
78
79 let response = self.client.post_xml("/api/user/login", &xml).await?;
80 let text = response.text().await?;
81
82 trace!("Login response: {}", text);
83
84 let result: Response = serde_xml_rs::from_str(&text)
85 .map_err(|e| Error::generic(format!("Failed to parse login response: {e}")))?;
86
87 if !result.is_success() {
88 let error_code = result.error_code().unwrap_or(-1);
89 let error_message = match error_code {
90 108001 => "Username wrong".to_string(),
91 108002 => "Password wrong".to_string(),
92 108006 => "Username or password wrong".to_string(),
93 108007 => "Too many login attempts".to_string(),
94 _ => result.error_message().unwrap_or("Login failed").to_string(),
95 };
96
97 return Err(Error::api(error_code, error_message));
98 }
99
100 self.client.session().mark_authenticated(username).await;
101
102 debug!("Login successful for user: {}", username);
103 Ok(())
104 }
105
106 pub async fn logout(&self) -> Result<()> {
108 debug!("Attempting logout");
109
110 let request = LogoutRequest::new();
111 let xml = serde_xml_rs::to_string(&request)
112 .map_err(|e| Error::generic(format!("Failed to serialize logout request: {e}")))?;
113
114 let response = self.client.post_xml("/api/user/logout", &xml).await?;
115 let text = response.text().await?;
116
117 trace!("Logout response: {}", text);
118
119 let result: Response = serde_xml_rs::from_str(&text)
120 .map_err(|e| Error::generic(format!("Failed to parse logout response: {e}")))?;
121
122 if !result.is_success() {
123 return Err(Error::api(
124 result.error_code().unwrap_or(-1),
125 result
126 .error_message()
127 .unwrap_or("Logout failed")
128 .to_string(),
129 ));
130 }
131
132 self.client.session().clear_session().await;
133
134 debug!("Logout successful");
135 Ok(())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::config::Config;
143
144 #[test]
145 fn test_auth_api_creation() {
146 let config = Config::default();
147 let client = crate::Client::new(config).unwrap();
148 let auth_api = client.auth();
149
150 assert_eq!(
151 std::mem::size_of_val(&auth_api),
152 std::mem::size_of::<&Client>()
153 );
154 }
155
156 #[test]
157 fn test_login_request_serialization() {
158 let request = LoginRequest::new(
159 "admin".to_string(),
160 "encoded_password".to_string(),
161 "4".to_string(),
162 );
163
164 let xml = serde_xml_rs::to_string(&request).unwrap();
165 assert!(xml.contains("<Username>admin</Username>"));
166 assert!(xml.contains("<Password>encoded_password</Password>"));
167 assert!(xml.contains("<password_type>4</password_type>"));
168 }
169
170 #[test]
171 fn test_logout_request_serialization() {
172 let request = LogoutRequest::new();
173 let xml = serde_xml_rs::to_string(&request).unwrap();
174 assert!(xml.contains("<Logout>1</Logout>"));
175 }
176}