gemini / server /src /modules /oauth.rs
yinming
feat: Antigravity API Proxy for HuggingFace Spaces
bbb1195
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<String>,
}
#[derive(Debug, Serialize, Deserialize)]
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(&params)
.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(&current_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
))
}