use jsonwebtoken::{DecodingKey, TokenData, Validation};
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, EmptyExtraTokenFields,
RedirectUrl, Scope, StandardTokenResponse, TokenUrl,
};
use crate::error::Error;
use crate::{client, EsiResult};
const AUTHORIZE_URL: &str = "https://login.eveonline.com/v2/oauth/authorize";
const TOKEN_URL: &str = "https://login.eveonline.com/v2/oauth/token";
const AUTHORIZATION_SERVER_URL: &str =
"https://login.eveonline.com/.well-known/oauth-authorization-server";
const LOGIN_URL: &str = "https://login.eveonline.com";
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct AuthenticationData {
pub login_url: String,
pub state: String,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct EveSsoMetaData {
pub authorization_endpoint: String,
pub code_challenge_methods_supported: Vec<String>,
pub issuer: String,
pub jwks_uri: String,
pub response_types_supported: Vec<String>,
pub revocation_endpoint: String,
pub revocation_endpoint_auth_methods_supported: Vec<String>,
pub token_endpoint: String,
pub token_endpoint_auth_methods_supported: Vec<String>,
pub token_endpoint_auth_signing_alg_values_supported: Vec<String>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub struct EveJwtKeys {
#[serde(rename = "SkipUnresolvedJsonWebKeys")]
pub skip_unresolved_json_web_keys: bool,
pub keys: Vec<EveJwtKey>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
#[serde(tag = "alg")]
pub enum EveJwtKey {
RS256 {
e: String,
kid: String,
kty: String,
n: String,
r#use: String,
},
ES256 {
crv: String,
kid: String,
kty: String,
r#use: String,
x: String,
y: String,
},
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct EveJwtClaims {
pub scp: Option<Vec<String>>,
pub jti: String,
pub kid: String,
pub sub: String,
pub azp: String,
pub tenant: String,
pub tier: String,
pub region: String,
pub aud: Vec<String>,
pub name: String,
pub owner: String,
pub exp: u64,
pub iat: u64,
pub iss: String,
}
pub fn create_login_url(
client_id: String,
client_secret: String,
callback_url: String,
scopes: Vec<String>,
) -> EsiResult<AuthenticationData> {
fn convert_scopes(scopes: Vec<String>) -> Vec<Scope> {
scopes.iter().map(|s| Scope::new(s.clone())).collect()
}
let client = BasicClient::new(
ClientId::new(client_id),
Some(ClientSecret::new(client_secret)),
AuthUrl::new(AUTHORIZE_URL.to_string())?,
Some(TokenUrl::new(TOKEN_URL.to_string())?),
)
.set_redirect_uri(RedirectUrl::new(callback_url)?);
let scopes = convert_scopes(scopes);
let (eve_oauth_url, csrf_token) = client
.authorize_url(CsrfToken::new_random)
.add_scopes(scopes)
.url();
Ok(AuthenticationData {
login_url: eve_oauth_url.to_string(),
state: csrf_token.secret().to_string(),
})
}
pub async fn get_token(
client_id: String,
client_secret: String,
code: String,
) -> EsiResult<StandardTokenResponse<EmptyExtraTokenFields, oauth2::basic::BasicTokenType>> {
let client = BasicClient::new(
ClientId::new(client_id),
Some(ClientSecret::new(client_secret)),
AuthUrl::new(AUTHORIZE_URL.to_string())?,
Some(TokenUrl::new(TOKEN_URL.to_string())?),
);
match client
.exchange_code(AuthorizationCode::new(code.to_string()))
.request_async(async_http_client)
.await
{
Ok(token) => Ok(token),
Err(err) => return Err(Error::new(err.to_string())),
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RefreshTokenResponse {
pub access_token: String,
pub token_type: String,
pub expires_in: i64,
pub refresh_token: String,
}
pub async fn token_refresh(token_id: &str) -> EsiResult<RefreshTokenResponse> {
println!("Token ID: {}", token_id);
Err(Error::new("Failed to refresh token".to_string()))
}
pub async fn validate_token(token: &str) -> EsiResult<TokenData<EveJwtClaims>> {
async fn get_eve_jwt_keys() -> EsiResult<EveJwtKeys> {
let sso_meta_data_url = AUTHORIZATION_SERVER_URL;
let res: EveSsoMetaData = client().get(sso_meta_data_url).send().await?.json().await?;
let response = client().get(res.jwks_uri).send().await?.json().await?;
Ok(response)
}
let jwk_keys = get_eve_jwt_keys().await?;
let jwk_key = match select_key(jwk_keys.keys) {
Some(key) => key,
None => return Err(Error::new("Failed to find RS256 key".to_string())),
};
let jwk_n: String;
let jwk_e: String;
if let EveJwtKey::RS256 {
e,
kid: _,
kty: _,
n,
r#use: _,
} = jwk_key
{
jwk_n = n;
jwk_e = e;
} else {
return Err(Error::new("Failed to find RS256 key".to_string()));
}
let mut validation = Validation::new(jsonwebtoken::Algorithm::RS256);
validation.set_audience(&["EVE Online"]);
validation.set_issuer(&[LOGIN_URL]);
let decoding_key = &DecodingKey::from_rsa_components(&jwk_n, &jwk_e)?;
let token = jsonwebtoken::decode::<EveJwtClaims>(&token, decoding_key, &validation)?;
Ok(token)
}
fn select_key(keys: Vec<EveJwtKey>) -> Option<EveJwtKey> {
for key in keys {
if let EveJwtKey::RS256 {
e: _,
kid: _,
kty: _,
n: _,
r#use: _,
} = &key
{
return Some(key);
}
}
None
}