Skip to main content

egs_api/api/types/
account.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Structure that holds all account data
6#[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/// Structure that holds all user data
33///
34/// Needed for login
35#[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    /// Creates new UserData Structure
62    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    /// Get access token
85    pub fn access_token(&self) -> Option<&str> {
86        self.access_token.as_deref()
87    }
88
89    /// Get refresh token
90    pub fn refresh_token(&self) -> Option<&str> {
91        self.refresh_token.as_deref()
92    }
93
94    /// Set access token
95    pub fn set_access_token(&mut self, token: Option<String>) {
96        self.access_token = token;
97    }
98
99    /// Set refresh token
100    pub fn set_refresh_token(&mut self, token: Option<String>) {
101        self.refresh_token = token;
102    }
103
104    /// Updates only the present values in the existing user data
105    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/// Account info returned by bulk account ID lookup.
161#[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        // account_id left as None
209
210        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/// Response from `GET /account/api/oauth/verify` — token introspection.
294///
295/// Returns details about the current OAuth token including account info,
296/// client info, expiration times, and optionally granted permissions.
297#[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/// A single permission entry from token verification.
319#[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}