oauth_toolkit/
providers.rs1use serde::{Deserialize, Serialize};
8
9pub mod endpoints {
14 pub const GITHUB_AUTHORIZE_URL: &str = "https://github.com/login/oauth/authorize";
16 pub const GITHUB_TOKEN_URL: &str = "https://github.com/login/oauth/access_token";
18 pub const GITHUB_USER_INFO_URL: &str = "https://api.github.com/user";
20
21 pub const GOOGLE_AUTHORIZE_URL: &str = "https://accounts.google.com/o/oauth2/v2/auth";
23 pub const GOOGLE_TOKEN_URL: &str = "https://oauth2.googleapis.com/token";
25 pub const GOOGLE_USER_INFO_URL: &str = "https://www.googleapis.com/oauth2/v2/userinfo";
27
28 pub const YAHOO_AUTHORIZE_URL: &str = "https://api.login.yahoo.com/oauth2/request_auth";
30 pub const YAHOO_TOKEN_URL: &str = "https://api.login.yahoo.com/oauth2/get_token";
32
33 pub const AOL_AUTHORIZE_URL: &str = "https://api.login.aol.com/oauth2/request_auth";
35 pub const AOL_TOKEN_URL: &str = "https://api.login.aol.com/oauth2/get_token";
37
38 pub const FASTMAIL_AUTHORIZE_URL: &str = "https://app.fastmail.com/oauth/authorize";
40 pub const FASTMAIL_TOKEN_URL: &str = "https://api.fastmail.com/oauth/token";
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub struct MailProvider {
47 pub auth_url: String,
49 pub token_url: String,
51 pub client_id: String,
53 pub imap_scopes: Vec<String>,
55 pub smtp_scopes: Vec<String>,
57 pub extra_scopes: Vec<String>,
60}
61
62impl MailProvider {
63 pub fn gmail(client_id: &str) -> Self {
66 Self {
67 auth_url: endpoints::GOOGLE_AUTHORIZE_URL.to_owned(),
68 token_url: endpoints::GOOGLE_TOKEN_URL.to_owned(),
69 client_id: client_id.to_owned(),
70 imap_scopes: vec!["https://mail.google.com/".into()],
71 smtp_scopes: vec!["https://mail.google.com/".into()],
72 extra_scopes: vec!["openid".into(), "email".into()],
73 }
74 }
75
76 pub fn outlook(client_id: &str, tenant: &str) -> Self {
79 Self {
80 auth_url: format!("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize"),
81 token_url: format!("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"),
82 client_id: client_id.to_owned(),
83 imap_scopes: vec!["https://outlook.office.com/IMAP.AccessAsUser.All".into()],
84 smtp_scopes: vec!["https://outlook.office.com/SMTP.Send".into()],
85 extra_scopes: vec!["offline_access".into()],
86 }
87 }
88
89 pub fn yahoo(client_id: &str) -> Self {
91 Self {
92 auth_url: endpoints::YAHOO_AUTHORIZE_URL.to_owned(),
93 token_url: endpoints::YAHOO_TOKEN_URL.to_owned(),
94 client_id: client_id.to_owned(),
95 imap_scopes: vec!["mail-w".into()],
96 smtp_scopes: vec!["mail-w".into()],
97 extra_scopes: vec![],
98 }
99 }
100
101 pub fn aol(client_id: &str) -> Self {
103 Self {
104 auth_url: endpoints::AOL_AUTHORIZE_URL.to_owned(),
105 token_url: endpoints::AOL_TOKEN_URL.to_owned(),
106 client_id: client_id.to_owned(),
107 imap_scopes: vec!["mail-w".into()],
108 smtp_scopes: vec!["mail-w".into()],
109 extra_scopes: vec![],
110 }
111 }
112
113 pub fn fastmail(client_id: &str) -> Self {
115 Self {
116 auth_url: endpoints::FASTMAIL_AUTHORIZE_URL.to_owned(),
117 token_url: endpoints::FASTMAIL_TOKEN_URL.to_owned(),
118 client_id: client_id.to_owned(),
119 imap_scopes: vec!["https://www.fastmail.com/dev/protocolIMAP".into()],
120 smtp_scopes: vec!["https://www.fastmail.com/dev/protocolSMTP".into()],
121 extra_scopes: vec![],
122 }
123 }
124
125 pub fn authorization_scopes(&self) -> Vec<String> {
128 let mut out: Vec<String> = Vec::new();
129 for scope in self
130 .imap_scopes
131 .iter()
132 .chain(self.smtp_scopes.iter())
133 .chain(self.extra_scopes.iter())
134 {
135 if !out.contains(scope) {
136 out.push(scope.clone());
137 }
138 }
139 out
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn gmail_preset_shape() {
149 let g = MailProvider::gmail("g");
150 assert_eq!(g.auth_url, endpoints::GOOGLE_AUTHORIZE_URL);
151 assert!(g.token_url.contains("googleapis"));
152 let scopes = g.authorization_scopes();
153 assert!(scopes.contains(&"https://mail.google.com/".to_string()));
154 assert!(scopes.contains(&"openid".to_string()));
155 assert_eq!(
157 scopes
158 .iter()
159 .filter(|s| *s == "https://mail.google.com/")
160 .count(),
161 1
162 );
163 }
164
165 #[test]
166 fn fastmail_preset_split_scopes() {
167 let f = MailProvider::fastmail("f");
168 assert!(f.token_url.contains("fastmail"));
169 assert_eq!(
170 f.imap_scopes,
171 vec!["https://www.fastmail.com/dev/protocolIMAP"]
172 );
173 assert_eq!(
174 f.smtp_scopes,
175 vec!["https://www.fastmail.com/dev/protocolSMTP"]
176 );
177 let scopes = f.authorization_scopes();
178 assert_eq!(scopes.len(), 2);
179 assert!(scopes.contains(&"https://www.fastmail.com/dev/protocolIMAP".to_string()));
180 assert!(scopes.contains(&"https://www.fastmail.com/dev/protocolSMTP".to_string()));
181 }
182
183 #[test]
184 fn gmail_vs_fastmail_scope_shapes_differ() {
185 let g = MailProvider::gmail("g");
187 let f = MailProvider::fastmail("f");
188 assert!(g.imap_scopes == g.smtp_scopes);
189 assert_ne!(f.imap_scopes, f.smtp_scopes);
190 }
191
192 #[test]
193 fn outlook_preset_tenant_aware() {
194 let o = MailProvider::outlook("o", "common");
195 assert!(o.auth_url.contains("login.microsoftonline.com/common"));
196 assert!(o.token_url.contains("login.microsoftonline.com/common"));
197 assert!(o.authorization_scopes().iter().any(|s| s.contains("IMAP")));
198 assert!(
199 o.authorization_scopes()
200 .iter()
201 .any(|s| s.contains("SMTP.Send"))
202 );
203 assert!(o.extra_scopes.contains(&"offline_access".to_string()));
204
205 let t = MailProvider::outlook("o", "9188040d-6c67-4c5b-b112-36a304b66dad");
206 assert!(t.auth_url.contains("9188040d"));
207 }
208
209 #[test]
210 fn yahoo_and_aol_presets() {
211 let y = MailProvider::yahoo("y");
212 assert!(y.auth_url.contains("login.yahoo.com"));
213 assert!(y.token_url.contains("login.yahoo.com"));
214 assert!(y.authorization_scopes().contains(&"mail-w".to_string()));
215
216 let a = MailProvider::aol("a");
217 assert!(a.auth_url.contains("login.aol.com"));
218 assert!(a.token_url.contains("login.aol.com"));
219 assert!(a.authorization_scopes().contains(&"mail-w".to_string()));
220 }
221
222 #[test]
223 fn gmail_shares_google_urls_with_social_module() {
224 assert_eq!(
227 MailProvider::gmail("x").auth_url,
228 crate::social::SocialProvider::Google.authorize_url()
229 );
230 assert_eq!(
231 MailProvider::gmail("x").token_url,
232 crate::social::SocialProvider::Google.token_url()
233 );
234 }
235
236 #[test]
237 fn authorization_scopes_order_preserved() {
238 let p = MailProvider {
239 auth_url: "https://x".into(),
240 token_url: "https://x".into(),
241 client_id: "c".into(),
242 imap_scopes: vec!["a".into(), "b".into()],
243 smtp_scopes: vec!["b".into(), "c".into()],
244 extra_scopes: vec!["d".into(), "a".into()],
245 };
246 assert_eq!(p.authorization_scopes(), vec!["a", "b", "c", "d"]);
247 }
248}