use serde::{Deserialize, Serialize}; // Google OAuth configuration const CLIENT_ID: &str = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com"; const CLIENT_SECRET: &str = "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"; const TOKEN_URL: &str = "https://oauth2.googleapis.com/token"; const USERINFO_URL: &str = "https://www.googleapis.com/oauth2/v2/userinfo"; #[derive(Debug, Serialize, Deserialize)] pub struct TokenResponse { pub access_token: String, pub expires_in: i64, #[serde(default)] pub token_type: String, #[serde(default)] pub refresh_token: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct UserInfo { pub email: String, pub name: Option, pub given_name: Option, pub family_name: Option, pub picture: Option, } impl UserInfo { /// Get best display name pub fn get_display_name(&self) -> Option { // Prefer name if let Some(name) = &self.name { if !name.trim().is_empty() { return Some(name.clone()); } } // If name is empty, try combining given_name and family_name match (&self.given_name, &self.family_name) { (Some(given), Some(family)) => Some(format!("{} {}", given, family)), (Some(given), None) => Some(given.clone()), (None, Some(family)) => Some(family.clone()), (None, None) => None, } } } /// Refresh access_token using refresh_token pub async fn refresh_access_token(refresh_token: &str) -> Result { let client = crate::utils::http::create_client(15); let params = [ ("client_id", CLIENT_ID), ("client_secret", CLIENT_SECRET), ("refresh_token", refresh_token), ("grant_type", "refresh_token"), ]; crate::modules::logger::log_info("Refreshing token..."); let response = client .post(TOKEN_URL) .form(¶ms) .send() .await .map_err(|e| format!("Refresh request failed: {}", e))?; if response.status().is_success() { let token_data = response .json::() .await .map_err(|e| format!("Failed to parse refresh data: {}", e))?; crate::modules::logger::log_info(&format!("Token refreshed! Valid for: {} seconds", token_data.expires_in)); Ok(token_data) } else { let error_text = response.text().await.unwrap_or_default(); Err(format!("Refresh failed: {}", error_text)) } } /// Get user info pub async fn get_user_info(access_token: &str) -> Result { let client = crate::utils::http::create_client(15); let response = client .get(USERINFO_URL) .bearer_auth(access_token) .send() .await .map_err(|e| format!("User info request failed: {}", e))?; if response.status().is_success() { response.json::() .await .map_err(|e| format!("Failed to parse user info: {}", e)) } else { let error_text = response.text().await.unwrap_or_default(); Err(format!("Failed to get user info: {}", error_text)) } } /// Check and refresh token if needed /// Returns the latest access_token pub async fn ensure_fresh_token( current_token: &crate::models::TokenData, ) -> Result { let now = chrono::Local::now().timestamp(); // If no expiry time, or still has more than 5 minutes validity, return directly if current_token.expiry_timestamp > now + 300 { return Ok(current_token.clone()); } // Need to refresh crate::modules::logger::log_info("Token about to expire, refreshing..."); let response = refresh_access_token(¤t_token.refresh_token).await?; // Construct new TokenData Ok(crate::models::TokenData::new( response.access_token, current_token.refresh_token.clone(), // Refresh doesn't always return new refresh_token response.expires_in, current_token.email.clone(), current_token.project_id.clone(), // Keep original project_id None, // session_id will be generated in token_manager )) }