eversal_lib/esi/
auth.rs

1use jsonwebtoken::{DecodingKey, TokenData, Validation};
2use oauth2::basic::BasicClient;
3use oauth2::reqwest::async_http_client;
4use oauth2::{
5  AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields,
6  RedirectUrl, Scope, StandardTokenResponse, TokenUrl,
7};
8
9use crate::error::Error;
10use crate::esi::{client, LibResult};
11
12const AUTHORIZE_URL: &str = "https://login.eveonline.com/v2/oauth/authorize";
13const TOKEN_URL: &str = "https://login.eveonline.com/v2/oauth/token";
14const AUTHORIZATION_SERVER_URL: &str =
15  "https://login.eveonline.com/.well-known/oauth-authorization-server";
16const LOGIN_URL: &str = "https://login.eveonline.com";
17
18#[derive(Debug, serde::Serialize, serde::Deserialize)]
19pub struct AuthenticationData {
20  pub login_url: String,
21  pub state: String,
22}
23
24#[derive(Debug, serde::Serialize, serde::Deserialize)]
25pub struct EveSsoMetaData {
26  pub authorization_endpoint: String,
27  pub code_challenge_methods_supported: Vec<String>,
28  pub issuer: String,
29  pub jwks_uri: String,
30  pub response_types_supported: Vec<String>,
31  pub revocation_endpoint: String,
32  pub revocation_endpoint_auth_methods_supported: Vec<String>,
33  pub token_endpoint: String,
34  pub token_endpoint_auth_methods_supported: Vec<String>,
35  pub token_endpoint_auth_signing_alg_values_supported: Vec<String>,
36}
37
38#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
39pub struct EveJwtKeys {
40  #[serde(rename = "SkipUnresolvedJsonWebKeys")]
41  pub skip_unresolved_json_web_keys: bool,
42  pub keys: Vec<EveJwtKey>,
43}
44
45#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
46#[serde(tag = "alg")]
47pub enum EveJwtKey {
48  RS256 {
49    e: String,
50    kid: String,
51    kty: String,
52    n: String,
53    r#use: String,
54  },
55  ES256 {
56    crv: String,
57    kid: String,
58    kty: String,
59    r#use: String,
60    x: String,
61    y: String,
62  },
63}
64
65#[derive(Debug, serde::Serialize, serde::Deserialize)]
66pub struct EveJwtClaims {
67  pub scp: Option<Vec<String>>,
68  pub jti: String,
69  pub kid: String,
70  pub sub: String,
71  pub azp: String,
72  pub tenant: String,
73  pub tier: String,
74  pub region: String,
75  pub aud: Vec<String>,
76  pub name: String,
77  pub owner: String,
78  pub exp: u64,
79  pub iat: u64,
80  pub iss: String,
81}
82
83pub fn create_login_url(
84  client_id: String,
85  client_secret: String,
86  callback_url: String,
87  scopes: Vec<String>,
88) -> LibResult<AuthenticationData> {
89  fn convert_scopes(scopes: Vec<String>) -> Vec<Scope> {
90    scopes.iter().map(|s| Scope::new(s.clone())).collect()
91  }
92
93  let client = BasicClient::new(
94    ClientId::new(client_id),
95    Some(ClientSecret::new(client_secret)),
96    AuthUrl::new(AUTHORIZE_URL.to_string())?,
97    Some(TokenUrl::new(TOKEN_URL.to_string())?),
98  )
99  .set_redirect_uri(RedirectUrl::new(callback_url)?);
100
101  let scopes = convert_scopes(scopes);
102
103  let (eve_oauth_url, csrf_token) = client
104    .authorize_url(CsrfToken::new_random)
105    .add_scopes(scopes)
106    .url();
107
108  Ok(AuthenticationData {
109    login_url: eve_oauth_url.to_string(),
110    state: csrf_token.secret().to_string(),
111  })
112}
113
114pub async fn get_token(
115  client_id: String,
116  client_secret: String,
117  code: String,
118) -> LibResult<StandardTokenResponse<EmptyExtraTokenFields, oauth2::basic::BasicTokenType>> {
119  let client = BasicClient::new(
120    ClientId::new(client_id),
121    Some(ClientSecret::new(client_secret)),
122    AuthUrl::new(AUTHORIZE_URL.to_string())?,
123    Some(TokenUrl::new(TOKEN_URL.to_string())?),
124  );
125
126  match client
127    .exchange_code(AuthorizationCode::new(code.to_string()))
128    .request_async(async_http_client)
129    .await
130  {
131    Ok(token) => Ok(token),
132    Err(err) => return Err(Error::new(500, err.to_string())),
133  }
134}
135
136// pub async fn refresh_token(
137//   client_id: String,
138//   refresh_token: String,
139// ) -> LibResult<StandardTokenResponse<EmptyExtraTokenFields, oauth2::basic::BasicTokenType>> {
140//   let client = BasicClient::new(
141//     ClientId::new(client_id),
142//     None,
143//     AuthUrl::new(AUTHORIZE_URL.to_string())?,
144//     Some(TokenUrl::new(TOKEN_URL.to_string())?),
145//   );
146
147//   match client
148//     .exchange_refresh_token(&RefreshToken::new(refresh_token))
149//     .request_async(async_http_client)
150//     .await
151//   {
152//     Ok(token) => Ok(token),
153//     Err(err) => return Err(EsiError::new(err.to_string())),
154//   }
155// }
156
157#[derive(Debug, serde::Serialize, serde::Deserialize)]
158pub struct RefreshTokenResponse {
159  pub access_token: String,
160  pub token_type: String,
161  pub expires_in: i64,
162  pub refresh_token: String,
163}
164
165pub async fn token_refresh(token_id: &str) -> LibResult<RefreshTokenResponse> {
166  println!("Token ID: {}", token_id);
167  // This doesn't work, just return an error for now
168  Err(Error::new(500, "Failed to refresh token".to_string()))
169  // let client_id = env::var("ESI_CLIENT_ID").expect("ESI_CLIENT_ID must be set");
170  // let client_secret = env::var("ESI_CLIENT_SECRET").expect("ESI_CLIENT_SECRET must be set");
171  // let url = format!("{}?grant_type=refresh_token&refresh_token={}", TOKEN_URL, refresh_token);
172
173  // let b64 = general_purpose::STANDARD.encode(format!("{}:{}", client_id, client_secret).as_bytes());
174  // println!("URL: {}", url);
175
176  // let res: RefreshTokenResponse = client().post(url)
177  //   .header("Content-Type", "application/x-www-form-urlencoded")
178  //   .header("Host", "login.eveonline.com")
179  //   .header("Authorization", format!("Basic {}", b64))
180  //   .send().await?.json().await?;
181
182  // Ok(res)
183}
184
185pub async fn validate_token(token: &str) -> LibResult<TokenData<EveJwtClaims>> {
186  async fn get_eve_jwt_keys() -> LibResult<EveJwtKeys> {
187    let sso_meta_data_url = AUTHORIZATION_SERVER_URL;
188
189    let res: EveSsoMetaData = client().get(sso_meta_data_url).send().await?.json().await?;
190
191    let response = client().get(res.jwks_uri).send().await?.json().await?;
192
193    Ok(response)
194  }
195
196  let jwk_keys = get_eve_jwt_keys().await?;
197  let jwk_key = match select_key(jwk_keys.keys) {
198    Some(key) => key,
199    None => return Err(Error::new(500, "Failed to find RS256 key".to_string())),
200  };
201
202  let jwk_n: String;
203  let jwk_e: String;
204
205  if let EveJwtKey::RS256 {
206    e,
207    kid: _,
208    kty: _,
209    n,
210    r#use: _,
211  } = jwk_key
212  {
213    jwk_n = n;
214    jwk_e = e;
215  } else {
216    return Err(Error::new(500, "Failed to find RS256 key".to_string()));
217  }
218
219  let mut validation = Validation::new(jsonwebtoken::Algorithm::RS256);
220  validation.set_audience(&["EVE Online"]);
221  validation.set_issuer(&[LOGIN_URL]);
222
223  let decoding_key = &DecodingKey::from_rsa_components(&jwk_n, &jwk_e)?;
224
225  let token = jsonwebtoken::decode::<EveJwtClaims>(&token, decoding_key, &validation)?;
226
227  Ok(token)
228}
229
230fn select_key(keys: Vec<EveJwtKey>) -> Option<EveJwtKey> {
231  for key in keys {
232    if let EveJwtKey::RS256 {
233      e: _,
234      kid: _,
235      kty: _,
236      n: _,
237      r#use: _,
238    } = &key
239    {
240      return Some(key);
241    }
242  }
243
244  None
245}