Spaces:
Sleeping
Sleeping
| 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"; | |
| pub struct TokenResponse { | |
| pub access_token: String, | |
| pub expires_in: i64, | |
| pub token_type: String, | |
| pub refresh_token: Option<String>, | |
| } | |
| pub struct UserInfo { | |
| pub email: String, | |
| pub name: Option<String>, | |
| pub given_name: Option<String>, | |
| pub family_name: Option<String>, | |
| pub picture: Option<String>, | |
| } | |
| impl UserInfo { | |
| /// Get best display name | |
| pub fn get_display_name(&self) -> Option<String> { | |
| // 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<TokenResponse, String> { | |
| 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::<TokenResponse>() | |
| .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<UserInfo, String> { | |
| 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::<UserInfo>() | |
| .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<crate::models::TokenData, String> { | |
| 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 | |
| )) | |
| } | |