Skip to main content

egs_api/facade/
auth.rs

1use crate::EpicGames;
2use crate::api::error::EpicAPIError;
3use crate::api::types::account::{TokenVerification, UserData};
4use log::{error, info, warn};
5
6impl EpicGames {
7    /// Check whether the user is logged in.
8    ///
9    /// Returns `true` if the access token exists and has more than 600 seconds
10    /// remaining before expiry.
11    pub fn is_logged_in(&self) -> bool {
12        if let Some(exp) = self.egs.user_data.expires_at {
13            let now = chrono::offset::Utc::now();
14            let td = exp - now;
15            if td.num_seconds() > 600 {
16                return true;
17            }
18        }
19        false
20    }
21
22    /// Returns a clone of the current session state.
23    ///
24    /// The returned [`UserData`] implements `Serialize` / `Deserialize`,
25    /// so you can persist it to disk and restore it later with
26    /// [`set_user_details`](Self::set_user_details).
27    pub fn user_details(&self) -> UserData {
28        self.egs.user_data.clone()
29    }
30
31    /// Restore session state from a previously saved [`UserData`].
32    ///
33    /// Only merges `Some` fields — existing values are preserved for any
34    /// field that is `None` in the input. Call [`login`](Self::login)
35    /// afterward to refresh the access token.
36    pub fn set_user_details(&mut self, user_details: UserData) {
37        self.egs.user_data.update(user_details);
38    }
39
40    /// Like [`auth_code`](Self::auth_code), but returns a `Result` instead of swallowing errors.
41    pub async fn try_auth_code(
42        &mut self,
43        exchange_token: Option<String>,
44        authorization_code: Option<String>,
45    ) -> Result<bool, EpicAPIError> {
46        self.egs
47            .start_session(exchange_token, authorization_code)
48            .await
49    }
50
51    /// Authenticate with an authorization code or exchange token.
52    ///
53    /// Returns `true` on success, `false` on failure. Returns `None` on API errors.
54    pub async fn auth_code(
55        &mut self,
56        exchange_token: Option<String>,
57        authorization_code: Option<String>,
58    ) -> bool {
59        self.try_auth_code(exchange_token, authorization_code)
60            .await
61            .unwrap_or(false)
62    }
63
64    /// Invalidate the current session and log out.
65    pub async fn logout(&mut self) -> bool {
66        self.egs.invalidate_sesion().await
67    }
68
69    /// Like [`login`](Self::login), but returns a `Result` instead of swallowing errors.
70    pub async fn try_login(&mut self) -> Result<bool, EpicAPIError> {
71        if let Some(exp) = self.egs.user_data.expires_at {
72            let now = chrono::offset::Utc::now();
73            let td = exp - now;
74            if td.num_seconds() > 600 {
75                info!("Trying to re-use existing login session... ");
76                let resumed = self.egs.resume_session().await.map_err(|e| {
77                    warn!("{}", e);
78                    e
79                })?;
80                if resumed {
81                    info!("Logged in");
82                    return Ok(true);
83                }
84                return Ok(false);
85            }
86        }
87        info!("Logging in...");
88        if let Some(exp) = self.egs.user_data.refresh_expires_at {
89            let now = chrono::offset::Utc::now();
90            let td = exp - now;
91            if td.num_seconds() > 600 {
92                let started = self.egs.start_session(None, None).await.map_err(|e| {
93                    error!("{}", e);
94                    e
95                })?;
96                if started {
97                    info!("Logged in");
98                    return Ok(true);
99                }
100                return Ok(false);
101            }
102        }
103        Ok(false)
104    }
105
106    /// Resume session using the saved refresh token.
107    ///
108    /// Returns `true` on success, `false` if the refresh token has expired or is invalid.
109    /// Unlike [`try_login`](Self::try_login), this method falls through to
110    /// refresh-token login if session resume fails.
111    pub async fn login(&mut self) -> bool {
112        if let Some(exp) = self.egs.user_data.expires_at {
113            let now = chrono::offset::Utc::now();
114            let td = exp - now;
115            if td.num_seconds() > 600 {
116                info!("Trying to re-use existing login session... ");
117                match self.egs.resume_session().await {
118                    Ok(b) => {
119                        if b {
120                            info!("Logged in");
121                            return true;
122                        }
123                        return false;
124                    }
125                    Err(e) => {
126                        warn!("{}", e)
127                    }
128                };
129            }
130        }
131        info!("Logging in...");
132        if let Some(exp) = self.egs.user_data.refresh_expires_at {
133            let now = chrono::offset::Utc::now();
134            let td = exp - now;
135            if td.num_seconds() > 600 {
136                match self.egs.start_session(None, None).await {
137                    Ok(b) => {
138                        if b {
139                            info!("Logged in");
140                            return true;
141                        }
142                        return false;
143                    }
144                    Err(e) => {
145                        error!("{}", e)
146                    }
147                }
148            }
149        }
150        false
151    }
152
153    /// Like [`auth_client_credentials`](Self::auth_client_credentials), but returns a `Result` instead of swallowing errors.
154    pub async fn try_auth_client_credentials(&mut self) -> Result<bool, EpicAPIError> {
155        self.egs.start_client_credentials_session().await
156    }
157
158    /// Authenticate with client credentials (app-level, no user context).
159    ///
160    /// Uses the launcher's public client ID/secret to obtain an access token
161    /// without any user interaction. The resulting session has limited
162    /// permissions — it can query public endpoints (catalog, service status,
163    /// currencies) but cannot access user-specific data (library, entitlements).
164    ///
165    /// Returns `true` on success, `false` on failure.
166    pub async fn auth_client_credentials(&mut self) -> bool {
167        self.try_auth_client_credentials().await.unwrap_or(false)
168    }
169
170    /// Authenticate via session ID (SID) from the Epic web login flow.
171    ///
172    /// Performs the multi-step web exchange: set-sid → CSRF → exchange code,
173    /// then starts a session with the resulting code. Returns `true` on success.
174    pub async fn auth_sid(&mut self, sid: &str) -> Result<bool, EpicAPIError> {
175        self.egs.auth_sid(sid).await
176    }
177
178    /// Verify the current OAuth token and get account/session info.
179    ///
180    /// Returns `None` on any error.
181    pub async fn verify_access_token(&self, include_perms: bool) -> Option<TokenVerification> {
182        self.try_verify_access_token(include_perms).await.ok()
183    }
184
185    /// Like [`verify_access_token`](Self::verify_access_token), but returns a `Result` instead of swallowing errors.
186    pub async fn try_verify_access_token(
187        &self,
188        include_perms: bool,
189    ) -> Result<TokenVerification, EpicAPIError> {
190        self.egs.verify_token(include_perms).await
191    }
192}