pub mod callback_server;
pub mod device_flow;
pub mod discovery;
pub mod flow;
pub mod token_store;
pub use callback_server::{start_callback_server, OAuthCallbackResult};
pub use device_flow::{
execute_device_flow, start_device_flow, DeviceCodeResponse, DeviceTokenResponse,
};
pub use discovery::{
clear_all_discovered_oauth_cache, clear_discovered_oauth_cache, discover_oauth_from_mcp_server,
};
pub use flow::{
build_authorization_url, exchange_code_for_token, generate_pkce_pair, generate_state,
is_token_expired, refresh_access_token, OAuthTokenResponse, PkcePair,
};
pub use token_store::{
clear_token, get_valid_token, load_token, save_token, TokenMetadata, TokenResult,
};
use crate::config::OAuthConfig;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
lazy_static::lazy_static! {
static ref OAUTH_LOCKS: Arc<Mutex<HashMap<String, Arc<Mutex<()>>>>> = Arc::new(Mutex::new(HashMap::new()));
}
pub async fn start_oauth_flow(config: &OAuthConfig) -> Result<OAuthCallbackResult, anyhow::Error> {
config
.validate()
.map_err(|e| anyhow::anyhow!("OAuth config validation failed: {}", e))?;
let pkce = generate_pkce_pair();
let state = generate_state();
let result = start_callback_server(config, state, pkce.code_verifier).await?;
Ok(result)
}
pub async fn get_access_token(
config: &OAuthConfig,
server_name: &str,
force_refresh: bool,
) -> Result<Option<String>, anyhow::Error> {
let lock = {
let mut locks = OAUTH_LOCKS.lock().await;
locks
.entry(server_name.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone()
};
let _guard = lock.lock().await;
if !force_refresh {
crate::log_debug!(
"🔍 GET_ACCESS_TOKEN: Checking for existing valid token for server_name='{}'",
server_name
);
let valid_token = get_valid_token(server_name, config.refresh_buffer_seconds)
.await
.map_err(|e| anyhow::anyhow!("Failed to check token: {}", e))?;
if let Some(metadata) = valid_token {
crate::log_debug!(
"✅ GET_ACCESS_TOKEN: Found valid token, token_prefix='{}...'",
metadata.access_token.chars().take(10).collect::<String>()
);
return Ok(Some(metadata.access_token));
} else {
crate::log_debug!("⚠️ GET_ACCESS_TOKEN: No valid token found, starting OAuth flow");
}
}
let issuer = &config.authorization_url;
if issuer.contains("github.com") {
crate::log_debug!(
"🔍 Using GitHub Device Flow for authentication, server_name='{}'",
server_name
);
crate::log_debug!(
"Using GitHub Device Flow for authentication, server_name='{}'",
server_name
);
crate::log_debug!("🔍 Calling execute_device_flow...");
let access_token = execute_device_flow(config, server_name)
.await
.map_err(|e| anyhow::anyhow!("Device flow failed: {}", e))?;
crate::log_debug!(
"✅ Device flow returned! access_token prefix='{}...'",
access_token.chars().take(10).collect::<String>()
);
crate::log_debug!(
"Device flow completed, access_token prefix='{}...'",
access_token.chars().take(10).collect::<String>()
);
let metadata = TokenMetadata {
server_name: server_name.to_string(),
access_token: access_token.clone(),
refresh_token: None,
expires_at: 0, scopes: config.scopes.clone(),
};
crate::log_debug!("🔍 Saving token for server_name='{}'...", server_name);
if let Err(e) = save_token(server_name, &metadata).await {
crate::log_debug!("❌ Failed to save token: {}", e);
crate::log_error!("Failed to save token: {}", e);
} else {
crate::log_debug!("✅ Token save completed");
}
crate::log_debug!("🔍 Verifying token was saved...");
match load_token(server_name).await {
Ok(Some(saved)) => {
crate::log_debug!(
"✅ Token verified saved: server_name='{}', token_prefix='{}...'",
server_name,
saved.access_token.chars().take(10).collect::<String>()
);
crate::log_debug!(
"Token verified saved: server_name='{}', token_prefix='{}...'",
server_name,
saved.access_token.chars().take(10).collect::<String>()
);
}
Ok(None) => {
crate::log_debug!("❌ Token was NOT found in storage after save attempt!");
crate::log_error!("Token was NOT found in storage after save attempt!");
}
Err(e) => {
crate::log_debug!("❌ Failed to verify token storage: {}", e);
crate::log_error!("Failed to verify token storage: {}", e);
}
}
return Ok(Some(access_token));
}
let result = start_oauth_flow(config).await?;
match result {
OAuthCallbackResult::Success { access_token, .. } => Ok(Some(access_token)),
OAuthCallbackResult::Error { error, description } => Err(anyhow::anyhow!(
"OAuth failed: {} - {}",
error,
description.unwrap_or_default()
)),
OAuthCallbackResult::Cancelled => Ok(None),
OAuthCallbackResult::Timeout => Err(anyhow::anyhow!("OAuth timed out")),
}
}
pub async fn is_authenticated(server_name: &str, refresh_buffer_seconds: u64) -> bool {
get_valid_token(server_name, refresh_buffer_seconds)
.await
.map(|m| m.is_some())
.unwrap_or(false)
}