use crate::error::{Result, RustDriveSyncError};
use std::path::Path;
pub use yup_oauth2;
use yup_oauth2::{
authenticator::Authenticator, hyper::client::HttpConnector, hyper_rustls::HttpsConnector,
read_application_secret, InstalledFlowAuthenticator, InstalledFlowReturnMethod,
};
pub struct DriveAuthenticator {
pub(crate) authenticator: Authenticator<HttpsConnector<HttpConnector>>,
scopes: Vec<String>,
}
impl DriveAuthenticator {
pub async fn new<P: AsRef<Path>>(
credentials_path: P,
token_path: P,
scopes: Vec<String>,
) -> Result<Self> {
let credentials_path_ref = credentials_path.as_ref();
let token_path_ref = token_path.as_ref();
if !credentials_path_ref.exists() {
return Err(RustDriveSyncError::CredentialsNotFound {
path: credentials_path_ref.display().to_string(),
});
}
let secret = read_application_secret(credentials_path_ref)
.await
.map_err(|e| RustDriveSyncError::AuthenticationFailed {
message: format!("Erro ao ler credenciais: {}", e),
})?;
let auth = InstalledFlowAuthenticator::builder(secret, InstalledFlowReturnMethod::HTTPRedirect)
.persist_tokens_to_disk(token_path_ref)
.build()
.await
.map_err(|e| RustDriveSyncError::AuthenticationFailed {
message: format!("Erro ao criar autenticador: {}", e),
})?;
Ok(Self {
authenticator: auth,
scopes,
})
}
pub async fn get_token(&self) -> Result<String> {
let token: yup_oauth2::AccessToken = self
.authenticator
.token(&self.scopes)
.await
.map_err(|e| RustDriveSyncError::AuthenticationFailed {
message: format!("Erro ao obter token: {}", e),
})?;
Ok(token.token().unwrap_or_default().to_string())
}
pub async fn has_valid_token(&self) -> bool {
let result: std::result::Result<yup_oauth2::AccessToken, _> =
self.authenticator.token(&self.scopes).await;
result.is_ok()
}
pub async fn refresh_token(&self) -> Result<String> {
self.get_token().await
}
}
pub async fn authenticate<P: AsRef<Path>>(
credentials_path: P,
token_path: P,
scopes: Vec<String>,
) -> Result<()> {
tracing::info!("Iniciando fluxo de autenticação OAuth2...");
let authenticator = DriveAuthenticator::new(credentials_path, token_path, scopes.clone()).await?;
let _token = authenticator.get_token().await?;
tracing::info!("✓ Autenticação concluída com sucesso!");
tracing::info!("Token salvo. Você já pode usar o RustDriveSync.");
Ok(())
}
pub async fn revoke_token<P: AsRef<Path>>(token_path: P) -> Result<()> {
let token_path_ref = token_path.as_ref();
if token_path_ref.exists() {
std::fs::remove_file(token_path_ref).map_err(|e| {
RustDriveSyncError::AuthenticationFailed {
message: format!("Erro ao revogar token: {}", e),
}
})?;
tracing::info!("✓ Token revogado com sucesso");
} else {
tracing::warn!("Nenhum token encontrado para revogar");
}
Ok(())
}
pub async fn check_auth_status<P: AsRef<Path> + Clone>(
credentials_path: P,
token_path: P,
scopes: Vec<String>,
) -> Result<()> {
let token_path_ref = token_path.as_ref();
if !token_path_ref.exists() {
println!("❌ Não autenticado");
println!("Execute 'rustdrivesync auth' para autenticar");
return Ok(());
}
let token_path_display = token_path_ref.display().to_string();
let authenticator = DriveAuthenticator::new(credentials_path, token_path, scopes).await?;
if authenticator.has_valid_token().await {
println!("✓ Autenticado com sucesso");
println!("Token válido encontrado em: {}", token_path_display);
} else {
println!("⚠ Token expirado ou inválido");
println!("Execute 'rustdrivesync auth' para renovar");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_missing_credentials() {
let result = DriveAuthenticator::new(
"/nonexistent/credentials.json",
"/tmp/token.json",
vec!["https://www.googleapis.com/auth/drive.file".to_string()],
)
.await;
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, RustDriveSyncError::CredentialsNotFound { .. }));
}
}
}