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}