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 {
51 pub(super) token_path: Option<PathBuf>,
52 pub(super) authent_token: Option<Authent>,
53 pub(super) user_agent: String,
54 pub(super) scopes: Vec<Scope>,
55 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,
60 pub(super) client_secret: String,
61}
62
63#[derive(Serialize, Deserialize)]
64pub struct Authent {
65 pub token: StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>,
66}
67
68#[derive(Serialize, Deserialize, Debug, Clone)]
69pub struct Verify {
70 #[serde(rename = "CharacterID")]
71 pub character_id: usize,
72 #[serde(rename = "CharacterName")]
73 pub character_name: String,
74}
75
76impl Oauth2Authent {
77 pub async fn authenticate(&mut self) -> Result<()> {
79 match self.handle_existing_token().await {
80 Ok(()) => {
81 if self.need_refresh(24)? {
82 let _ = self.refresh_token().await;
83 }
84 Ok(())
85 }
86 Err(EveEsiError::NoTokenFound) | Err(EveEsiError::ScopesChanged) => {
87 self.connect().await?;
88 self.serialize_token().await?;
89 Ok(())
90 }
91 other => other,
92 }
93 }
94 async fn serialize_token(&self) -> Result<()> {
96 if let Some(path) = &self.token_path {
97 let mut file = File::create(path).await?;
98 Ok(file
99 .write_all(serde_json::to_string(&self.authent_token)?.as_bytes())
100 .await?)
101 } else {
102 Ok(())
103 }
104 }
105
106 async fn deserialize_token(&mut self) -> Result<()> {
108 if let Some(path) = &self.token_path {
109 let mut file = File::open(path).await?;
110
111 let mut buf = String::new();
112 file.read_to_string(&mut buf).await?;
113 self.authent_token = serde_json::from_str(buf.as_str())?;
114 Ok(())
115 } else {
116 Ok(())
117 }
118 }
119
120 pub async fn verify(&self) -> Result<Verify> {
122 let token = self.get_token()?.unwrap();
123 let client = reqwest::Client::builder()
124 .user_agent(self.user_agent.as_str())
125 .build()?;
126
127 let r = client
128 .get(self.verify_url.as_str())
129 .bearer_auth(token.token.access_token().secret())
130 .send()
131 .await?;
132
133 let text_response = r.error_for_status()?.text().await?;
134
135 Ok(serde_json::from_str(text_response.as_str())?)
136 }
137
138 async fn refresh_token(&mut self) -> Result<()> {
140 let token = self.get_token()?.unwrap();
141 let client = self.build_client()?;
142 let token = client
143 .exchange_refresh_token(token.token.refresh_token().unwrap())
144 .request_async(async_http_client)
145 .await?;
146 self.authent_token = Some(Authent { token });
147 Ok(())
148 }
149
150 fn get_token(&self) -> Result<Option<&Authent>> {
152 if let Some(t) = &self.authent_token {
153 Ok(Some(t))
154 } else {
155 Err(EveEsiError::NoTokenFound)
156 }
157 }
158
159 fn build_client(&self) -> Result<Oauth2Client> {
160 let client_id = ClientId::new(self.client_id.clone());
164 let client_secret = ClientSecret::new(self.client_secret.clone());
165 let auth_url = AuthUrl::new(self.auth_url.clone()).unwrap();
166 let token_url = TokenUrl::new(self.token_url.clone()).unwrap();
167
168 Ok(
169 BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url))
170 .set_redirect_uri(RedirectUrl::new(self.callback_url.clone()).unwrap()),
171 )
172 }
173
174 async fn connect(&mut self) -> Result<()> {
176 let client = self.build_client()?;
177 let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
178
179 let (auth_url, csrf_state) = client
181 .authorize_url(CsrfToken::new_random)
182 .add_scopes(self.scopes.clone())
183 .set_pkce_challenge(pkce_challenge)
185 .url();
186
187 println!("Browse to: {}", auth_url);
188
189 let listener = TcpListener::bind("127.0.0.1:8569").await.unwrap();
191
192 if let Ok((mut stream, _)) = listener.accept().await {
193 let code;
194 let state;
195 {
196 let mut reader = BufReader::new(&mut stream);
197
198 let mut request_line = String::new();
199 reader.read_line(&mut request_line).await.unwrap();
200
201 let redirect_url = request_line.split_whitespace().nth(1).unwrap();
202 let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
203
204 let code_pair = url
205 .query_pairs()
206 .find(|pair| {
207 let (key, _) = pair;
208 key == "code"
209 })
210 .unwrap();
211
212 let (_, value) = code_pair;
213 code = AuthorizationCode::new(value.into_owned());
214
215 let state_pair = url
216 .query_pairs()
217 .find(|pair| {
218 let (key, _) = pair;
219 key == "state"
220 })
221 .unwrap();
222
223 let (_, value) = state_pair;
224 state = CsrfToken::new(value.into_owned());
225 }
226
227 let message = "Go back to your terminal :)";
228 let response = format!(
229 "HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
230 message.len(),
231 message
232 );
233 stream.write_all(response.as_bytes()).await.unwrap();
234
235 debug!("EVE returned the following code:\n{}\n", code.secret());
236 debug!(
237 "EVE returned the following state:\n{} (expected `{}`)\n",
238 state.secret(),
239 csrf_state.secret()
240 );
241
242 let token_res = client
244 .exchange_code(code)
245 .set_pkce_verifier(pkce_verifier)
246 .request_async(async_http_client)
247 .await?;
248
249 debug!("EVE returned the following token:\n{:?}\n", token_res);
250
251 self.authent_token = Some(Authent { token: token_res });
252 Ok(())
253 } else {
254 Err(EveEsiError::NoTokenFound)
256 }
257 }
258
259 pub fn get_authent(self) -> Result<Authent> {
261 if let Some(auth) = self.authent_token {
262 Ok(auth)
263 } else {
264 Err(EveEsiError::NoTokenFound)
265 }
266 }
267
268 pub async fn _delete_token(&self) -> Result<()> {
270 if let Some(path) = &self.token_path {
271 Ok(std::fs::remove_file(path)?)
272 } else {
273 Ok(())
274 }
275 }
276
277 fn get_token_scopes(&self) -> Result<Option<&Vec<Scope>>> {
278 Ok(self.get_token()?.unwrap().token.scopes())
279 }
280
281 fn get_token_expires_in(&self) -> Result<Option<Duration>> {
282 Ok(self.get_token()?.unwrap().token.expires_in())
283 }
284
285 fn need_refresh(&self, hours: u64) -> Result<bool> {
286 if let Some(duration) = self.get_token_expires_in()? {
287 if duration > Duration::from_secs(60 * 60 * hours) {
288 return Ok(true);
289 }
290 }
291 Ok(false)
292 }
293
294 fn have_scopes_changed(&self) -> Result<bool> {
295 let existant = self.get_token_scopes()?;
296 let required = Some(&self.scopes);
297
298 let existant_set: Option<HashSet<&Scope>> = existant.map(|v| HashSet::from_iter(v.iter()));
299 let requiret_set = required.map(|v| HashSet::from_iter(v.iter()));
300
301 Ok(existant_set == requiret_set)
302 }
303
304 async fn handle_existing_token(&mut self) -> Result<()> {
305 if self.authent_token.is_some() {
307 self.verify().await?;
308 }
309 else if self.deserialize_token().await.is_ok() {
311 if let Ok(v) = self.verify().await {
312 debug!("logged as {:#?}", v);
313 } else {
314 if self.refresh_token().await.is_err() {
316 return Err(EveEsiError::NoTokenFound);
317 }
318 }
319 } else {
320 return Err(EveEsiError::NoTokenFound);
321 }
322 if self.have_scopes_changed()? {
323 return Err(EveEsiError::ScopesChanged);
324 }
325 Ok(())
326 }
327}