Skip to main content

huawei_dongle_api/models/
auth.rs

1//! Authentication models
2
3use super::enums::{LockStatus, LoginStatus};
4use serde::{Deserialize, Serialize};
5
6/// Login state response from `/api/user/state-login`
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LoginState {
9    /// Password encoding type (0=BASE64, 3=BASE64_after_change, 4=SHA256)
10    #[serde(rename = "password_type")]
11    pub password_type: String,
12
13    /// External password type
14    #[serde(rename = "extern_password_type")]
15    pub extern_password_type: String,
16
17    /// History login flag
18    #[serde(rename = "history_login_flag")]
19    pub history_login_flag: String,
20
21    /// Current login state (-1=not_logged_in, 0=logged_in, -2=repeat_login_required)
22    #[serde(rename = "State")]
23    pub state: LoginStatus,
24
25    /// Guide modify password page flag
26    #[serde(rename = "guidemodifypwdpageflag")]
27    pub guide_modify_pwd_page_flag: String,
28
29    /// RSA padding type
30    #[serde(rename = "rsapadingtype")]
31    pub rsa_padding_type: String,
32
33    /// Number of accounts
34    #[serde(rename = "accounts_number")]
35    pub accounts_number: String,
36
37    /// WiFi password same with web password
38    #[serde(rename = "wifipwdsamewithwebpwd")]
39    pub wifi_pwd_same_with_web_pwd: String,
40
41    /// Remaining wait time
42    #[serde(rename = "remainwaittime")]
43    pub remain_wait_time: String,
44
45    /// Lock status (0=unlocked, >0=locked)
46    #[serde(rename = "lockstatus")]
47    pub lock_status: LockStatus,
48
49    /// Force skip guide
50    #[serde(rename = "forceskipguide")]
51    pub force_skip_guide: String,
52
53    /// Username
54    #[serde(rename = "username")]
55    pub username: String,
56
57    /// First login flag
58    #[serde(rename = "firstlogin")]
59    pub first_login: String,
60
61    /// User level
62    #[serde(rename = "userlevel")]
63    pub user_level: String,
64}
65
66/// Login request
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct LoginRequest {
69    /// Username (typically "admin")
70    #[serde(rename = "Username")]
71    pub username: String,
72
73    /// Encoded password (BASE64 or SHA256)
74    #[serde(rename = "Password")]
75    pub password: String,
76
77    /// Password type from login state
78    #[serde(rename = "password_type")]
79    pub password_type: String,
80}
81
82/// Logout request
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct LogoutRequest {
85    /// Logout type (usually "1")
86    #[serde(rename = "Logout")]
87    pub logout: String,
88}
89
90impl LoginState {
91    /// Check if user is currently logged in
92    pub fn is_logged_in(&self) -> bool {
93        self.state.is_logged_in()
94    }
95
96    /// Check if account is locked
97    pub fn is_locked(&self) -> bool {
98        self.lock_status.is_locked()
99    }
100
101    /// Get password encoding type
102    pub fn password_encoding(&self) -> PasswordEncoding {
103        match self.password_type.as_str() {
104            "0" => PasswordEncoding::Base64,
105            "3" => PasswordEncoding::Base64AfterChange,
106            "4" => PasswordEncoding::Sha256,
107            _ => PasswordEncoding::Unknown,
108        }
109    }
110}
111
112/// Password encoding types
113#[derive(Debug, Clone, PartialEq)]
114pub enum PasswordEncoding {
115    /// BASE64 encoding
116    Base64,
117    /// BASE64 encoding after password change
118    Base64AfterChange,
119    /// SHA256 encoding (most common)
120    Sha256,
121    /// Unknown encoding type
122    Unknown,
123}
124
125impl LoginRequest {
126    /// Create a new login request
127    pub fn new(username: String, password: String, password_type: String) -> Self {
128        Self {
129            username,
130            password,
131            password_type,
132        }
133    }
134}
135
136impl LogoutRequest {
137    /// Create a new logout request
138    pub fn new() -> Self {
139        Self {
140            logout: "1".to_string(),
141        }
142    }
143}
144
145impl Default for LogoutRequest {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_login_state_parsing() {
157        let xml = r#"
158        <response>
159            <password_type>4</password_type>
160            <extern_password_type>1</extern_password_type>
161            <history_login_flag>0</history_login_flag>
162            <State>-1</State>
163            <guidemodifypwdpageflag>0</guidemodifypwdpageflag>
164            <rsapadingtype>1</rsapadingtype>
165            <accounts_number>1</accounts_number>
166            <wifipwdsamewithwebpwd>0</wifipwdsamewithwebpwd>
167            <remainwaittime>0</remainwaittime>
168            <lockstatus>0</lockstatus>
169            <forceskipguide>0</forceskipguide>
170            <username></username>
171            <firstlogin>0</firstlogin>
172            <userlevel></userlevel>
173        </response>"#;
174
175        let state: LoginState = serde_xml_rs::from_str(xml).unwrap();
176        assert_eq!(state.password_type, "4");
177        assert_eq!(state.state, LoginStatus::NotLoggedIn);
178        assert!(!state.is_logged_in());
179        assert!(!state.is_locked());
180        assert_eq!(state.password_encoding(), PasswordEncoding::Sha256);
181    }
182
183    #[test]
184    fn test_login_request_serialization() {
185        let request = LoginRequest::new(
186            "admin".to_string(),
187            "encoded_password".to_string(),
188            "4".to_string(),
189        );
190
191        let xml = serde_xml_rs::to_string(&request).unwrap();
192        assert!(xml.contains("<Username>admin</Username>"));
193        assert!(xml.contains("<Password>encoded_password</Password>"));
194        assert!(xml.contains("<password_type>4</password_type>"));
195    }
196
197    #[test]
198    fn test_password_encoding_detection() {
199        let mut state = LoginState {
200            password_type: "0".to_string(),
201            state: LoginStatus::NotLoggedIn,
202            lock_status: LockStatus::Unlocked,
203            extern_password_type: "1".to_string(),
204            history_login_flag: "0".to_string(),
205            guide_modify_pwd_page_flag: "0".to_string(),
206            rsa_padding_type: "1".to_string(),
207            accounts_number: "1".to_string(),
208            wifi_pwd_same_with_web_pwd: "0".to_string(),
209            remain_wait_time: "0".to_string(),
210            force_skip_guide: "0".to_string(),
211            username: "".to_string(),
212            first_login: "0".to_string(),
213            user_level: "".to_string(),
214        };
215
216        assert_eq!(state.password_encoding(), PasswordEncoding::Base64);
217
218        state.password_type = "4".to_string();
219        assert_eq!(state.password_encoding(), PasswordEncoding::Sha256);
220    }
221}