use serde::{Deserialize, Serialize};
use super::base::BaseOAuthProvider;
use crate::oauth2::types::{OAuthError, OAuthToken, OAuthUserInfo, ProviderConfig};
pub struct GoogleProvider {
base: BaseOAuthProvider,
}
impl GoogleProvider {
pub fn new(config: &ProviderConfig) -> Result<Self, OAuthError> {
Ok(Self {
base: BaseOAuthProvider::new(
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
config,
"https://www.googleapis.com/oauth2/v2/userinfo".to_string(),
)?,
})
}
#[must_use]
pub fn authorization_url(&self) -> (String, String, String) {
self.base.authorization_url(&["openid", "email", "profile"])
}
pub async fn exchange_code(
&self,
code: &str,
pkce_verifier: &str,
) -> Result<OAuthToken, OAuthError> {
self.base.exchange_code(code, pkce_verifier).await
}
pub async fn fetch_user_info(&self, access_token: &str) -> Result<OAuthUserInfo, OAuthError> {
let json = self.base.fetch_user_info_json(access_token).await?;
let google_user: GoogleUserInfo = serde_json::from_value(json)
.map_err(|e| OAuthError::UserInfoFailed(format!("Failed to parse Google user info: {e}")))?;
Ok(OAuthUserInfo {
provider_user_id: google_user.id,
email: google_user.email,
name: google_user.name,
avatar_url: google_user.picture,
email_verified: google_user.verified_email.unwrap_or(false),
})
}
}
#[derive(Debug, Deserialize, Serialize)]
struct GoogleUserInfo {
id: String,
email: String,
verified_email: Option<bool>,
name: Option<String>,
picture: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_authorization_url_generation() {
let config = ProviderConfig {
client_id: "test-client-id".to_string(),
client_secret: "test-client-secret".to_string(),
redirect_uri: "http://localhost:3000/auth/google/callback".to_string(),
scopes: vec!["openid".to_string(), "email".to_string()],
auth_url: None,
token_url: None,
userinfo_url: None,
};
let provider = GoogleProvider::new(&config).unwrap();
let (auth_url, csrf_state, pkce_verifier) = provider.authorization_url();
assert!(auth_url.starts_with("https://accounts.google.com"));
assert!(auth_url.contains("client_id=test-client-id"));
assert!(auth_url.contains("redirect_uri"));
assert!(auth_url.contains("scope=openid"));
assert!(!csrf_state.is_empty());
assert!(!pkce_verifier.is_empty());
}
}