Skip to main content

max / goingson

2.9 KB · 87 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 scopes: vec![
31 "https://mail.google.com/".to_string(), // Full Gmail access (IMAP/SMTP)
32 "openid".to_string(),
33 "email".to_string(),
34 "profile".to_string(),
35 ],
36 uses_jmap: false,
37 jmap_session_url: None,
38 imap_server: Some("imap.gmail.com".to_string()),
39 imap_port: Some(993),
40 smtp_server: Some("smtp.gmail.com".to_string()),
41 smtp_port: Some(587),
42 userinfo_url: Some("https://www.googleapis.com/oauth2/v2/userinfo".to_string()),
43 email_json_path: vec!["email"],
44 },
45 }
46 }
47 }
48
49 impl OAuthProvider for GoogleProvider {
50 fn id(&self) -> &'static str {
51 "google"
52 }
53
54 fn display_name(&self) -> &'static str {
55 "Google / Gmail"
56 }
57
58 fn config(&self) -> &OAuthProviderConfig {
59 &self.config
60 }
61
62 fn client_id(&self) -> &str {
63 &self.client_id
64 }
65
66 fn client_secret(&self) -> Option<&str> {
67 Some(&self.client_secret)
68 }
69
70 fn customize_auth_url(&self, url: &mut String) {
71 // Google-specific: request refresh token and always show consent
72 url.push_str("&access_type=offline");
73 url.push_str("&prompt=consent");
74 }
75
76 // Uses default exchange_code, refresh_token, get_user_email implementations
77 }
78
79 /// Generates an XOAUTH2 authentication string for IMAP/SMTP.
80 ///
81 /// Format: base64("user=" + email + "\x01auth=Bearer " + access_token + "\x01\x01")
82 pub fn generate_xoauth2_string(email: &str, access_token: &str) -> String {
83 use base64::{engine::general_purpose::STANDARD, Engine};
84 let auth_string = format!("user={}\x01auth=Bearer {}\x01\x01", email, access_token);
85 STANDARD.encode(auth_string.as_bytes())
86 }
87