pub(crate) mod callback;
pub mod oauth1;
pub mod oauth2;
pub mod pending;
use crate::config::Config;
use crate::error::{Result, XurlError};
use crate::store::TokenStore;
#[allow(clippy::struct_field_names)]
pub struct Auth {
pub token_store: TokenStore,
info_url: String,
client_id: String,
client_secret: String,
auth_url: String,
token_url: String,
redirect_uri: String,
app_name: String,
}
impl Auth {
#[must_use]
pub fn new(cfg: &Config) -> Self {
let ts = TokenStore::with_credentials(&cfg.client_id, &cfg.client_secret);
let mut client_id = cfg.client_id.clone();
let mut client_secret = cfg.client_secret.clone();
let app_name = cfg.app_name.clone();
let app = ts.resolve_app(&app_name);
if client_id.is_empty() {
client_id.clone_from(&app.client_id);
}
if client_secret.is_empty() {
client_secret.clone_from(&app.client_secret);
}
Self {
token_store: ts,
info_url: cfg.info_url.clone(),
client_id,
client_secret,
auth_url: cfg.auth_url.clone(),
token_url: cfg.token_url.clone(),
redirect_uri: cfg.redirect_uri.clone(),
app_name,
}
}
pub fn with_app_name(&mut self, app_name: &str) {
self.app_name = app_name.to_string();
let app = self.token_store.resolve_app(app_name);
if self.client_id.is_empty() {
self.client_id = app.client_id.clone();
}
if self.client_secret.is_empty() {
self.client_secret = app.client_secret.clone();
}
}
pub fn get_oauth1_header(
&self,
method: &str,
url_str: &str,
additional_params: Option<&std::collections::BTreeMap<String, String>>,
) -> Result<String> {
let token = self
.token_store
.get_oauth1_tokens()
.ok_or_else(|| XurlError::auth("TokenNotFound: OAuth1 token not found"))?;
let oauth1_token = token
.oauth1
.as_ref()
.ok_or_else(|| XurlError::auth("TokenNotFound: OAuth1 token not found"))?;
oauth1::build_oauth1_header(method, url_str, oauth1_token, additional_params)
}
pub fn get_oauth2_header(&mut self, username: &str) -> Result<String> {
let token = if username.is_empty() {
self.token_store.get_first_oauth2_token().cloned()
} else {
self.token_store.get_oauth2_token(username).cloned()
};
if token.is_none() {
let access_token = self.oauth2_flow(username)?;
return Ok(format!("Bearer {access_token}"));
}
let access_token = self.refresh_oauth2_token(username)?;
Ok(format!("Bearer {access_token}"))
}
pub fn oauth2_flow(&mut self, username: &str) -> Result<String> {
oauth2::run_oauth2_flow(self, username)
}
pub fn refresh_oauth2_token(&mut self, username: &str) -> Result<String> {
oauth2::refresh_oauth2_token(self, username)
}
pub fn remote_oauth2_step1(&self, pending_path: &std::path::Path) -> Result<String> {
oauth2::run_remote_step1(self, pending_path)
}
pub fn remote_oauth2_step2(
&mut self,
redirect_url: &str,
username: &str,
pending_path: &std::path::Path,
) -> Result<String> {
oauth2::run_remote_step2(self, redirect_url, username, pending_path)
}
pub fn get_bearer_token_header(&self) -> Result<String> {
let token = self
.token_store
.get_bearer_token()
.ok_or_else(|| XurlError::auth("TokenNotFound: bearer token not found"))?;
let bearer = token
.bearer
.as_ref()
.ok_or_else(|| XurlError::auth("TokenNotFound: bearer token not found"))?;
Ok(format!("Bearer {bearer}"))
}
pub(crate) fn fetch_username(&self, access_token: &str) -> Result<String> {
let client = reqwest::blocking::Client::new();
let resp = client
.get(&self.info_url)
.header("Authorization", format!("Bearer {access_token}"))
.send()
.map_err(|e| XurlError::auth_with_cause("NetworkError", &e))?;
let body: serde_json::Value = resp
.json()
.map_err(|e| XurlError::auth_with_cause("JSONDeserializationError", &e))?;
body.get("data")
.and_then(|d| d.get("username"))
.and_then(|u| u.as_str())
.map(std::string::ToString::to_string)
.ok_or_else(|| {
XurlError::auth("UsernameNotFound: username not found when fetching username")
})
}
#[allow(dead_code)] #[must_use]
pub fn with_token_store(mut self, token_store: TokenStore) -> Self {
let old_app = self.token_store.resolve_app(&self.app_name);
let new_app = token_store.resolve_app(&self.app_name);
if self.client_id == old_app.client_id {
self.client_id = new_app.client_id.clone();
}
if self.client_secret == old_app.client_secret {
self.client_secret = new_app.client_secret.clone();
}
self.token_store = token_store;
self
}
#[allow(dead_code)] #[must_use]
pub fn token_store(&self) -> &TokenStore {
&self.token_store
}
#[must_use]
pub fn app_name(&self) -> &str {
&self.app_name
}
#[must_use]
pub fn client_id(&self) -> &str {
&self.client_id
}
#[must_use]
pub fn client_secret(&self) -> &str {
&self.client_secret
}
#[must_use]
pub fn auth_url(&self) -> &str {
&self.auth_url
}
#[must_use]
pub fn token_url(&self) -> &str {
&self.token_url
}
#[must_use]
pub fn redirect_uri(&self) -> &str {
&self.redirect_uri
}
}