diff --git a/src/infrastructure/api.rs b/src/infrastructure/api.rs index 35be4df..b116078 100644 --- a/src/infrastructure/api.rs +++ b/src/infrastructure/api.rs @@ -1,10 +1,9 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use reqwest::Client; use yup_oauth2::{ authenticator::Authenticator, hyper::client::connect::HttpConnector, - hyper_rustls::HttpsConnector, ApplicationSecret, InstalledFlowAuthenticator, - InstalledFlowReturnMethod, + hyper_rustls::HttpsConnector, InstalledFlowAuthenticator, InstalledFlowReturnMethod, }; use crate::domain::models::*; @@ -37,7 +36,7 @@ pub struct ApiClient { const SCOPES: &[&str] = &["https://www.googleapis.com/auth/tasks"]; impl ApiClient { - pub async fn new(client_id: String, client_secret: String) -> Result { + pub async fn new(secret_path: impl AsRef) -> Result { let token_path = dirs::config_dir() .unwrap_or_else(|| PathBuf::from(".")) .join("task_app") @@ -47,17 +46,9 @@ impl ApiClient { std::fs::create_dir_all(parent).ok(); } - let secret = ApplicationSecret { - client_id, - client_secret, - auth_uri: "https://accounts.google.com/o/oauth2/v2/auth".to_string(), - token_uri: "https://oauth2.googleapis.com/token".to_string(), - redirect_uris: vec!["http://127.0.0.1:8080/".to_string()], - client_email: None, - client_x509_cert_url: None, - project_id: None, - ..Default::default() - }; + let secret = yup_oauth2::read_application_secret(secret_path) + .await + .map_err(|e| ApiError::Auth(format!("Failed to read secret file: {}", e)))?; let authenticator = InstalledFlowAuthenticator::builder( secret, diff --git a/src/main.rs b/src/main.rs index 3594c2e..69c7ae6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod infrastructure; mod ui; use std::io; +use std::path::PathBuf; use std::sync::Arc; use crossterm::event::{self, Event}; @@ -19,9 +20,33 @@ use crate::infrastructure::api::ApiClient; use crate::infrastructure::db::Db; use crate::ui::{draw, AppView, NetworkStatus}; +fn find_secret_file() -> Option { + if let Ok(path) = std::env::var("GOOGLE_CLIENT_SECRET_FILE") { + let p = PathBuf::from(&path); + if p.exists() { + return Some(p); + } + } + + let config_path = dirs::config_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("task_app") + .join("client_secret.json"); + if config_path.exists() { + return Some(config_path); + } + + let local_path = PathBuf::from("client_secret.json"); + if local_path.exists() { + return Some(local_path); + } + + None +} + fn main() -> io::Result<()> { let db_path = dirs::data_dir() - .unwrap_or_else(|| std::path::PathBuf::from(".")) + .unwrap_or_else(|| PathBuf::from(".")) .join("task_app") .join("tasks.db"); @@ -29,6 +54,17 @@ fn main() -> io::Result<()> { let db = Arc::new(Db::new(db_path.to_str().unwrap()).expect("Failed to open database")); + let secret_path = find_secret_file().unwrap_or_else(|| { + eprintln!( + "ERROR: Google client secret file not found.\n\ + Place client_secret.json in one of:\n\ + - Set GOOGLE_CLIENT_SECRET_FILE env var\n\ + - ~/.config/task_app/client_secret.json\n\ + - ./client_secret.json (current directory)" + ); + std::process::exit(1); + }); + enable_raw_mode()?; let mut stdout = io::stdout(); stdout.execute(EnterAlternateScreen)?; @@ -39,12 +75,9 @@ fn main() -> io::Result<()> { tokio::runtime::Runtime::new() .unwrap() .block_on(async { - ApiClient::new( - std::env::var("GOOGLE_CLIENT_ID").unwrap_or_default(), - std::env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(), - ) - .await - .expect("Failed to create ApiClient") + ApiClient::new(&secret_path) + .await + .expect("Failed to create ApiClient") }), );