1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[allow(missing_docs)]
7#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct AccountData {
10 pub age_group: String,
11 pub can_update_display_name: bool,
12 pub company: String,
13 pub country: String,
14 pub display_name: String,
15 pub email: String,
16 pub email_verified: bool,
17 pub failed_login_attempts: i64,
18 pub headless: bool,
19 pub id: String,
20 pub last_display_name_change: String,
21 pub last_login: String,
22 pub last_name: String,
23 pub minor_expected: bool,
24 pub minor_status: String,
25 pub minor_verified: bool,
26 pub name: String,
27 pub number_of_display_name_changes: i64,
28 pub preferred_language: String,
29 pub tfa_enabled: bool,
30}
31
32#[allow(missing_docs)]
36#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct UserData {
38 pub(crate) access_token: Option<String>,
39 pub expires_in: Option<i64>,
40 pub expires_at: Option<DateTime<Utc>>,
41 pub token_type: Option<String>,
42 pub(crate) refresh_token: Option<String>,
43 pub refresh_expires: Option<i64>,
44 pub refresh_expires_at: Option<DateTime<Utc>>,
45 pub account_id: Option<String>,
46 pub client_id: Option<String>,
47 pub internal_client: Option<bool>,
48 pub client_service: Option<String>,
49 #[serde(rename = "displayName")]
50 pub display_name: Option<String>,
51 pub app: Option<String>,
52 pub in_app_id: Option<String>,
53 pub device_id: Option<String>,
54 #[serde(rename = "errorMessage")]
55 pub error_message: Option<String>,
56 #[serde(rename = "errorCode")]
57 pub error_code: Option<String>,
58}
59
60impl UserData {
61 pub fn new() -> Self {
63 UserData {
64 access_token: None,
65 expires_in: None,
66 expires_at: None,
67 token_type: None,
68 refresh_token: None,
69 refresh_expires: None,
70 refresh_expires_at: None,
71 account_id: None,
72 client_id: None,
73 internal_client: None,
74 client_service: None,
75 display_name: None,
76 app: None,
77 in_app_id: None,
78 device_id: None,
79 error_message: None,
80 error_code: None,
81 }
82 }
83
84 pub fn access_token(&self) -> Option<&str> {
86 self.access_token.as_deref()
87 }
88
89 pub fn refresh_token(&self) -> Option<&str> {
91 self.refresh_token.as_deref()
92 }
93
94 pub fn set_access_token(&mut self, token: Option<String>) {
96 self.access_token = token;
97 }
98
99 pub fn set_refresh_token(&mut self, token: Option<String>) {
101 self.refresh_token = token;
102 }
103
104 pub fn update(&mut self, new: UserData) {
106 if let Some(n) = new.access_token {
107 self.access_token = Some(n)
108 }
109 if let Some(n) = new.expires_in {
110 self.expires_in = Some(n)
111 }
112 if let Some(n) = new.expires_at {
113 self.expires_at = Some(n)
114 }
115 if let Some(n) = new.token_type {
116 self.token_type = Some(n)
117 }
118 if let Some(n) = new.refresh_token {
119 self.refresh_token = Some(n)
120 }
121 if let Some(n) = new.refresh_expires {
122 self.refresh_expires = Some(n)
123 }
124 if let Some(n) = new.refresh_expires_at {
125 self.refresh_expires_at = Some(n)
126 }
127 if let Some(n) = new.account_id {
128 self.account_id = Some(n)
129 }
130 if let Some(n) = new.client_id {
131 self.client_id = Some(n)
132 }
133 if let Some(n) = new.internal_client {
134 self.internal_client = Some(n)
135 }
136 if let Some(n) = new.client_service {
137 self.client_service = Some(n)
138 }
139 if let Some(n) = new.display_name {
140 self.display_name = Some(n)
141 }
142 if let Some(n) = new.app {
143 self.app = Some(n)
144 }
145 if let Some(n) = new.in_app_id {
146 self.in_app_id = Some(n)
147 }
148 if let Some(n) = new.device_id {
149 self.device_id = Some(n)
150 }
151 if let Some(n) = new.error_message {
152 self.error_message = Some(n)
153 }
154 if let Some(n) = new.error_code {
155 self.error_code = Some(n)
156 }
157 }
158}
159
160#[allow(missing_docs)]
162#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct AccountInfo {
165 pub display_name: String,
166 pub external_auths: HashMap<String, ExternalAuth>,
167 pub id: String,
168}
169
170#[allow(missing_docs)]
171#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct ExternalAuth {
174 pub account_id: String,
175 pub auth_ids: Vec<AuthId>,
176 pub date_added: Option<String>,
177 pub avatar: Option<String>,
178 pub external_auth_id: Option<String>,
179 pub external_auth_id_type: Option<String>,
180 pub external_display_name: String,
181 #[serde(rename = "type")]
182 pub type_field: String,
183 pub external_auth_secondary_id: Option<String>,
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn user_data_new_defaults() {
192 let ud = UserData::new();
193 assert_eq!(ud.access_token, None);
194 assert_eq!(ud.refresh_token, None);
195 assert_eq!(ud.account_id, None);
196 assert_eq!(ud.display_name, None);
197 assert_eq!(ud.expires_at, None);
198 }
199
200 #[test]
201 fn user_data_update_merges_some_fields() {
202 let mut ud = UserData::new();
203 ud.access_token = Some("old_token".to_string());
204 ud.account_id = Some("id123".to_string());
205
206 let mut new_ud = UserData::new();
207 new_ud.access_token = Some("new_token".to_string());
208 ud.update(new_ud);
211 assert_eq!(ud.access_token, Some("new_token".to_string()));
212 assert_eq!(ud.account_id, Some("id123".to_string()));
213 }
214
215 #[test]
216 fn user_data_update_all_none_preserves() {
217 let mut ud = UserData::new();
218 ud.access_token = Some("token".to_string());
219 ud.account_id = Some("id".to_string());
220 ud.display_name = Some("user".to_string());
221
222 let original = ud.clone();
223 ud.update(UserData::new());
224 assert_eq!(ud, original);
225 }
226
227 #[test]
228 fn user_data_serialization_roundtrip() {
229 let mut ud = UserData::new();
230 ud.access_token = Some("tok123".to_string());
231 ud.display_name = Some("TestUser".to_string());
232 ud.account_id = Some("acc456".to_string());
233
234 let json = serde_json::to_string(&ud).unwrap();
235 let deserialized: UserData = serde_json::from_str(&json).unwrap();
236 assert_eq!(ud, deserialized);
237 }
238
239 #[test]
240 fn user_data_access_token_getters() {
241 let mut ud = UserData::new();
242 assert_eq!(ud.access_token(), None);
243 ud.set_access_token(Some("my_token".to_string()));
244 assert_eq!(ud.access_token(), Some("my_token"));
245 }
246
247 #[test]
248 fn user_data_refresh_token_getters() {
249 let mut ud = UserData::new();
250 assert_eq!(ud.refresh_token(), None);
251 ud.set_refresh_token(Some("refresh_tok".to_string()));
252 assert_eq!(ud.refresh_token(), Some("refresh_tok"));
253 }
254
255 #[test]
256 fn deserialize_token_verification() {
257 let json = r#"{"token":"abc123","sessionId":"sess1","tokenType":"bearer","clientId":"34a02cf8f4414e29b15921876da36f9a","internalClient":true,"clientService":"launcher","accountId":"8645b4947bbc4c0092a8b7236df169d1","expiresIn":28800,"expiresAt":"2026-02-16T20:00:00.000Z","authMethod":"exchange_code","displayName":"TestUser","app":"launcher","inAppId":"8645b4947bbc4c0092a8b7236df169d1","deviceId":"device1","perms":[{"resource":"account:public:account","action":1}]}"#;
258 let v: TokenVerification = serde_json::from_str(json).unwrap();
259 assert_eq!(
260 v.account_id,
261 Some("8645b4947bbc4c0092a8b7236df169d1".to_string())
262 );
263 assert_eq!(v.display_name, Some("TestUser".to_string()));
264 assert_eq!(v.auth_method, Some("exchange_code".to_string()));
265 let perms = v.perms.unwrap();
266 assert_eq!(perms.len(), 1);
267 assert_eq!(
268 perms[0].resource,
269 Some("account:public:account".to_string())
270 );
271 assert_eq!(perms[0].action, Some(1));
272 }
273
274 #[test]
275 fn deserialize_token_verification_no_perms() {
276 let json = r#"{"token":"abc","sessionId":"s1","tokenType":"bearer","clientId":"cid","internalClient":false,"clientService":"launcher","accountId":"aid","expiresIn":100,"expiresAt":"2026-01-01T00:00:00.000Z","authMethod":"refresh_token","displayName":"User","app":"launcher","inAppId":null,"deviceId":null,"perms":null}"#;
277 let v: TokenVerification = serde_json::from_str(json).unwrap();
278 assert_eq!(v.account_id, Some("aid".to_string()));
279 assert!(v.perms.is_none());
280 assert!(v.in_app_id.is_none());
281 }
282}
283
284#[allow(missing_docs)]
285#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct AuthId {
288 pub id: String,
289 #[serde(rename = "type")]
290 pub type_field: String,
291}
292
293#[allow(missing_docs)]
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299#[serde(rename_all = "camelCase")]
300pub struct TokenVerification {
301 pub token: Option<String>,
302 pub session_id: Option<String>,
303 pub token_type: Option<String>,
304 pub client_id: Option<String>,
305 pub internal_client: Option<bool>,
306 pub client_service: Option<String>,
307 pub account_id: Option<String>,
308 pub expires_in: Option<i64>,
309 pub expires_at: Option<String>,
310 pub auth_method: Option<String>,
311 pub display_name: Option<String>,
312 pub app: Option<String>,
313 pub in_app_id: Option<String>,
314 pub device_id: Option<String>,
315 pub perms: Option<Vec<TokenPermission>>,
316}
317
318#[allow(missing_docs)]
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct TokenPermission {
323 pub resource: Option<String>,
324 pub action: Option<u32>,
325}