Skip to main content

max / goingson

2.9 KB · 88 lines History Blame Raw
1 //! Google/Gmail OAuth2 provider.
2 //!
3 //! Implements OAuth2 for Gmail access via IMAP with XOAUTH2.
4 //! Uses default trait implementations for token exchange.
5 //!
6 //! See: https://developers.google.com/identity/protocols/oauth2
7
8 use crate::oauth::provider::{OAuthProvider, OAuthProviderConfig};
9
10 /// Google/Gmail OAuth2 provider.
11 ///
12 /// Uses FormBody authentication (client_id + client_secret in request body).
13 pub struct GoogleProvider {
14 client_id: String,
15 client_secret: String,
16 config: OAuthProviderConfig,
17 }
18
19 impl GoogleProvider {
20 /// Creates a new Google OAuth provider.
21 ///
22 /// Google requires a client secret even for desktop apps.
23 pub fn new(client_id: impl Into<String>, client_secret: impl Into<String>) -> Self {
24 Self {
25 client_id: client_id.into(),
26 client_secret: client_secret.into(),
27 config: OAuthProviderConfig {
28 auth_url: "https://accounts.google.com/o/oauth2/v2/auth".to_string(),
29 token_url: "https://oauth2.googleapis.com/token".to_string(),
30 revoke_url: Some("https://oauth2.googleapis.com/revoke".to_string()),
31 scopes: vec![
32 "https://mail.google.com/".to_string(), // Full Gmail access (IMAP/SMTP)
33 "openid".to_string(),
34 "email".to_string(),
35 "profile".to_string(),
36 ],
37 uses_jmap: false,
38 jmap_session_url: None,
39 imap_server: Some("imap.gmail.com".to_string()),
40 imap_port: Some(993),
41 smtp_server: Some("smtp.gmail.com".to_string()),
42 smtp_port: Some(587),
43 userinfo_url: Some("https://www.googleapis.com/oauth2/v2/userinfo".to_string()),
44 email_json_path: vec!["email"],
45 },
46 }
47 }
48 }
49
50 impl OAuthProvider for GoogleProvider {
51 fn id(&self) -> &'static str {
52 "google"
53 }
54
55 fn display_name(&self) -> &'static str {
56 "Google / Gmail"
57 }
58
59 fn config(&self) -> &OAuthProviderConfig {
60 &self.config
61 }
62
63 fn client_id(&self) -> &str {
64 &self.client_id
65 }
66
67 fn client_secret(&self) -> Option<&str> {
68 Some(&self.client_secret)
69 }
70
71 fn customize_auth_url(&self, url: &mut String) {
72 // Google-specific: request refresh token and always show consent
73 url.push_str("&access_type=offline");
74 url.push_str("&prompt=consent");
75 }
76
77 // Uses default exchange_code, refresh_token, get_user_email implementations
78 }
79
80 /// Generates an XOAUTH2 authentication string for IMAP/SMTP.
81 ///
82 /// Format: base64("user=" + email + "\x01auth=Bearer " + access_token + "\x01\x01")
83 pub fn generate_xoauth2_string(email: &str, access_token: &str) -> String {
84 use base64::{engine::general_purpose::STANDARD, Engine};
85 let auth_string = format!("user={}\x01auth=Bearer {}\x01\x01", email, access_token);
86 STANDARD.encode(auth_string.as_bytes())
87 }
88