aster_forge_external_auth/providers/
google.rs1use async_trait::async_trait;
8
9use crate::Result;
10use crate::driver::{
11 ExternalAuthAuthorizationStart, ExternalAuthCallback, ExternalAuthProfile,
12 ExternalAuthProviderConfig, ExternalAuthProviderDescriptor, ExternalAuthProviderDriver,
13 ExternalAuthProviderTestResult,
14};
15use crate::types::{ExternalAuthProtocol, ExternalAuthProviderKind};
16
17use super::oidc::OidcProviderDriver;
18
19const GOOGLE_ISSUER_URL: &str = "https://accounts.google.com";
21const GOOGLE_DEFAULT_SCOPES: &str = "openid profile email";
24
25#[derive(Default)]
27pub struct GoogleProviderDriver;
28
29impl GoogleProviderDriver {
30 pub fn new() -> Self {
32 Self
33 }
34}
35
36#[async_trait]
37impl ExternalAuthProviderDriver for GoogleProviderDriver {
38 fn kind(&self) -> ExternalAuthProviderKind {
39 ExternalAuthProviderKind::Google
40 }
41
42 fn descriptor(&self) -> ExternalAuthProviderDescriptor {
43 ExternalAuthProviderDescriptor {
44 kind: ExternalAuthProviderKind::Google,
45 protocol: ExternalAuthProtocol::Oidc,
46 display_name: "Google",
47 description: "Google OpenID Connect sign-in with fixed issuer and standard email_verified semantics.",
48 default_scopes: GOOGLE_DEFAULT_SCOPES,
49 issuer_url_required: false,
50 manual_endpoint_configuration_supported: false,
51 authorization_url_required: false,
52 token_url_required: false,
53 userinfo_url_required: false,
54 supports_discovery: true,
55 supports_pkce: true,
56 supports_email_verified_claim: true,
57 }
58 }
59
60 async fn start_authorization(
61 &self,
62 provider: &ExternalAuthProviderConfig,
63 redirect_uri: &str,
64 ) -> Result<ExternalAuthAuthorizationStart> {
65 OidcProviderDriver::new()
66 .start_authorization(&google_oidc_config(provider), redirect_uri)
67 .await
68 }
69
70 async fn exchange_callback(
71 &self,
72 provider: &ExternalAuthProviderConfig,
73 callback: ExternalAuthCallback,
74 ) -> Result<ExternalAuthProfile> {
75 OidcProviderDriver::new()
76 .exchange_callback(&google_oidc_config(provider), callback)
77 .await
78 }
79
80 async fn test_provider(
81 &self,
82 provider: &ExternalAuthProviderConfig,
83 ) -> Result<ExternalAuthProviderTestResult> {
84 let mut result = OidcProviderDriver::new()
85 .test_provider(&google_oidc_config(provider))
86 .await?;
87 result.provider = self.descriptor().display_name.to_string();
88 Ok(result)
89 }
90}
91
92fn google_oidc_config(provider: &ExternalAuthProviderConfig) -> ExternalAuthProviderConfig {
94 let mut provider = provider.clone();
95 provider.provider_kind = ExternalAuthProviderKind::Google;
96 provider.protocol = ExternalAuthProtocol::Oidc;
97 provider.issuer_url = provider
98 .issuer_url
99 .filter(|value| !value.trim().is_empty())
100 .or_else(|| Some(GOOGLE_ISSUER_URL.to_string()));
101 provider.authorization_url = None;
102 provider.token_url = None;
103 provider.userinfo_url = None;
104 provider.scopes = if provider.scopes.trim().is_empty() {
105 GOOGLE_DEFAULT_SCOPES.to_string()
106 } else {
107 provider.scopes.trim().to_string()
108 };
109 provider.subject_claim = provider.subject_claim.or_else(|| Some("sub".to_string()));
110 provider.display_name_claim = provider
111 .display_name_claim
112 .or_else(|| Some("name".to_string()));
113 provider.email_claim = provider.email_claim.or_else(|| Some("email".to_string()));
114 provider.email_verified_claim = provider
115 .email_verified_claim
116 .or_else(|| Some("email_verified".to_string()));
117 provider.avatar_url_claim = provider
118 .avatar_url_claim
119 .or_else(|| Some("picture".to_string()));
120 provider
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 fn provider() -> ExternalAuthProviderConfig {
128 ExternalAuthProviderConfig {
129 id: 1,
130 key: "google".to_string(),
131 provider_kind: ExternalAuthProviderKind::Google,
132 protocol: ExternalAuthProtocol::Oidc,
133 options: Default::default(),
134 issuer_url: None,
135 authorization_url: Some("https://ignored.example.com/auth".to_string()),
136 token_url: Some("https://ignored.example.com/token".to_string()),
137 userinfo_url: Some("https://ignored.example.com/userinfo".to_string()),
138 client_id: "client-id".to_string(),
139 client_secret: Some("secret".to_string()),
140 scopes: String::new(),
141 subject_claim: None,
142 username_claim: None,
143 display_name_claim: None,
144 email_claim: None,
145 email_verified_claim: None,
146 groups_claim: None,
147 avatar_url_claim: None,
148 outbound_http_user_agent: None,
149 }
150 }
151
152 #[test]
153 fn google_config_uses_fixed_defaults_and_claims() {
154 let config = google_oidc_config(&provider());
155
156 assert_eq!(config.provider_kind, ExternalAuthProviderKind::Google);
157 assert_eq!(config.protocol, ExternalAuthProtocol::Oidc);
158 assert_eq!(config.issuer_url.as_deref(), Some(GOOGLE_ISSUER_URL));
159 assert_eq!(config.authorization_url, None);
160 assert_eq!(config.token_url, None);
161 assert_eq!(config.userinfo_url, None);
162 assert_eq!(config.scopes, GOOGLE_DEFAULT_SCOPES);
163 assert_eq!(config.subject_claim.as_deref(), Some("sub"));
164 assert_eq!(config.display_name_claim.as_deref(), Some("name"));
165 assert_eq!(config.email_claim.as_deref(), Some("email"));
166 assert_eq!(
167 config.email_verified_claim.as_deref(),
168 Some("email_verified")
169 );
170 assert_eq!(config.avatar_url_claim.as_deref(), Some("picture"));
171 }
172
173 #[test]
174 fn google_config_keeps_test_issuer_override() {
175 let mut provider = provider();
176 provider.issuer_url = Some("http://127.0.0.1:3000".to_string());
177
178 let config = google_oidc_config(&provider);
179
180 assert_eq!(config.issuer_url.as_deref(), Some("http://127.0.0.1:3000"));
181 }
182}