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