eve_esi_api/api/authent/
oauth2.rs1use log::debug;
2
3use oauth2::basic::{BasicClient, BasicErrorResponseType, BasicTokenType};
4use oauth2::reqwest::async_http_client;
5use oauth2::{
6 AuthUrl, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields,
7 PkceCodeChallenge, RedirectUrl, RevocationErrorResponseType, Scope, StandardErrorResponse,
8 StandardRevocableToken, StandardTokenIntrospectionResponse, StandardTokenResponse,
9 TokenResponse, TokenUrl,
10};
11use serde::{Deserialize, Serialize};
12use std::collections::HashSet;
13use std::path::PathBuf;
14use std::time::Duration;
15use tokio::fs::File;
16use tokio::io::AsyncWriteExt;
17use tokio::io::BufReader;
18use tokio::io::{AsyncBufReadExt, AsyncReadExt};
19use tokio::net::TcpListener;
20use url::Url;
21
22use crate::errors::EveEsiError;
23
24use crate::Result;
25
26type Oauth2Client = Client<
27 StandardErrorResponse<BasicErrorResponseType>,
28 StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>,
29 BasicTokenType,
30 StandardTokenIntrospectionResponse<EmptyExtraTokenFields, BasicTokenType>,
31 StandardRevocableToken,
32 StandardErrorResponse<RevocationErrorResponseType>,
33>;
34
35pub(crate) struct Oauth2Authent {
38 pub(super) token_path: Option<PathBuf>,
39 pub(super) authent_token: Option<Authent>,
40 pub(super) user_agent: String,
41 pub(super) scopes: Vec<Scope>,
42 pub(super) verify_url: String, pub(super) auth_url: String, pub(super) token_url: String, pub(super) callback_url: String, pub(super) client_id: String,
47 pub(super) client_secret: String,
48}
49
50#[derive(Serialize, Deserialize)]
51pub struct Authent {
52 pub token: StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>,
53}
54
55#[derive(Serialize, Deserialize, Debug, Clone)]
56pub struct Verify {
57 #[serde(rename = "CharacterID")]
58 pub character_id: usize,
59 #[serde(rename = "CharacterName")]
60 pub character_name: String,
61}
62
63impl Oauth2Authent {
64 pub async fn authenticate(&mut self) -> Result<()> {
66 match self.handle_existing_token().await {
67 Ok(()) => {
68 if self.need_refresh(24)? {
69 let _ = self.refresh_token().await;
70 }
71 Ok(())
72 }
73 Err(EveEsiError::NoTokenFound) | Err(EveEsiError::ScopesChanged) => {
74 self.connect().await?;
75 self.serialize_token().await?;
76 Ok(())
77 }
78 other => other,
79 }
80 }
81 async fn serialize_token(&self) -> Result<()> {
83 if let Some(path) = &self.token_path {
84 let mut file = File::create(path).await?;
85 Ok(file
86 .write_all(serde_json::to_string(&self.authent_token)?.as_bytes())
87 .await?)
88 } else {
89 Ok(())
90 }
91 }
92
93 async fn deserialize_token(&mut self) -> Result<()> {
95 if let Some(path) = &self.token_path {
96 let mut file = File::open(path).await?;
97
98 let mut buf = String::new();
99 file.read_to_string(&mut buf).await?;
100 self.authent_token = serde_json::from_str(buf.as_str())?;
101 Ok(())
102 } else {
103 Ok(())
104 }
105 }
106
107 pub async fn verify(&self) -> Result<Verify> {
109 let token = self.get_token()?.unwrap();
110 let client = reqwest::Client::builder()
111 .user_agent(self.user_agent.as_str())
112 .build()?;
113
114 let r = client
115 .get(self.verify_url.as_str())
116 .bearer_auth(token.token.access_token().secret())
117 .send()
118 .await?;
119
120 let text_response = r.error_for_status()?.text().await?;
121
122 Ok(serde_json::from_str(text_response.as_str())?)
123 }
124
125 async fn refresh_token(&mut self) -> Result<()> {
127 let token = self.get_token()?.unwrap();
128 let client = self.build_client()?;
129 let token = client
130 .exchange_refresh_token(token.token.refresh_token().unwrap())
131 .request_async(async_http_client)
132 .await?;
133 self.authent_token = Some(Authent { token });
134 Ok(())
135 }
136
137 fn get_token(&self) -> Result<Option<&Authent>> {
139 if let Some(t) = &self.authent_token {
140 Ok(Some(t))
141 } else {
142 Err(EveEsiError::NoTokenFound)
143 }
144 }
145
146 fn build_client(&self) -> Result<Oauth2Client> {
147 let client_id = ClientId::new(self.client_id.clone());
151 let client_secret = ClientSecret::new(self.client_secret.clone());
152 let auth_url = AuthUrl::new(self.auth_url.clone()).unwrap();
153 let token_url = TokenUrl::new(self.token_url.clone()).unwrap();
154
155 Ok(
156 BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url))
157 .set_redirect_uri(RedirectUrl::new(self.callback_url.clone()).unwrap()),
158 )
159 }
160
161 async fn connect(&mut self) -> Result<()> {
163 let client = self.build_client()?;
164 let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
165
166 let (auth_url, csrf_state) = client
168 .authorize_url(CsrfToken::new_random)
169 .add_scopes(self.scopes.clone())
170 .set_pkce_challenge(pkce_challenge)
172 .url();
173
174 println!("Browse to: {}", auth_url);
175
176 let listener = TcpListener::bind("127.0.0.1:8569").await.unwrap();
178
179 if let Ok((mut stream, _)) = listener.accept().await {
180 let code;
181 let state;
182 {
183 let mut reader = BufReader::new(&mut stream);
184
185 let mut request_line = String::new();
186 reader.read_line(&mut request_line).await.unwrap();
187
188 let redirect_url = request_line.split_whitespace().nth(1).unwrap();
189 let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
190
191 let code_pair = url
192 .query_pairs()
193 .find(|pair| {
194 let (key, _) = pair;
195 key == "code"
196 })
197 .unwrap();
198
199 let (_, value) = code_pair;
200 code = AuthorizationCode::new(value.into_owned());
201
202 let state_pair = url
203 .query_pairs()
204 .find(|pair| {
205 let (key, _) = pair;
206 key == "state"
207 })
208 .unwrap();
209
210 let (_, value) = state_pair;
211 state = CsrfToken::new(value.into_owned());
212 }
213
214 let message = "Go back to your terminal :)";
215 let response = format!(
216 "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
217 message.len(),
218 message
219 );
220 stream.write_all(response.as_bytes()).await.unwrap();
221
222 debug!("EVE returned the following code:\n{}\n", code.secret());
223 debug!(
224 "EVE returned the following state:\n{} (expected `{}`)\n",
225 state.secret(),
226 csrf_state.secret()
227 );
228
229 let token_res = client
231 .exchange_code(code)
232 .set_pkce_verifier(pkce_verifier)
233 .request_async(async_http_client)
234 .await?;
235
236 debug!("EVE returned the following token:\n{:?}\n", token_res);
237
238 self.authent_token = Some(Authent { token: token_res });
239 Ok(())
240 } else {
241 Err(EveEsiError::NoTokenFound)
243 }
244 }
245
246 pub fn get_authent(self) -> Result<Authent> {
248 if let Some(auth) = self.authent_token {
249 Ok(auth)
250 } else {
251 Err(EveEsiError::NoTokenFound)
252 }
253 }
254
255 pub async fn _delete_token(&self) -> Result<()> {
257 if let Some(path) = &self.token_path {
258 Ok(std::fs::remove_file(path)?)
259 } else {
260 Ok(())
261 }
262 }
263
264 fn get_token_scopes(&self) -> Result<Option<&Vec<Scope>>> {
265 Ok(self.get_token()?.unwrap().token.scopes())
266 }
267
268 fn get_token_expires_in(&self) -> Result<Option<Duration>> {
269 Ok(self.get_token()?.unwrap().token.expires_in())
270 }
271
272 fn need_refresh(&self, hours: u64) -> Result<bool> {
273 if let Some(duration) = self.get_token_expires_in()? {
274 if duration > Duration::from_secs(60 * 60 * hours) {
275 return Ok(true);
276 }
277 }
278 Ok(false)
279 }
280
281 fn have_scopes_changed(&self) -> Result<bool> {
282 let existant = self.get_token_scopes()?;
283 let required = Some(&self.scopes);
284
285 let existant_set: Option<HashSet<&Scope>> = existant.map(|v| HashSet::from_iter(v.iter()));
286 let requiret_set = required.map(|v| HashSet::from_iter(v.iter()));
287
288 Ok(existant_set == requiret_set)
289 }
290
291 async fn handle_existing_token(&mut self) -> Result<()> {
292 if self.authent_token.is_some() {
294 self.verify().await?;
295 }
296 else if self.deserialize_token().await.is_ok() {
298 if let Ok(v) = self.verify().await {
299 debug!("logged as {:#?}", v);
300 } else {
301 if self.refresh_token().await.is_err() {
303 return Err(EveEsiError::NoTokenFound);
304 }
305 }
306 } else {
307 return Err(EveEsiError::NoTokenFound);
308 }
309 if self.have_scopes_changed()? {
310 return Err(EveEsiError::ScopesChanged);
311 }
312 Ok(())
313 }
314}