Skip to main content

better_auth_api/plugins/oauth/
providers.rs

1use serde_json::Value;
2use std::collections::HashMap;
3
4/// Configuration for the OAuth plugin, containing all registered providers.
5#[derive(Debug, Clone, Default)]
6pub struct OAuthConfig {
7    pub providers: HashMap<String, OAuthProvider>,
8}
9
10/// User information extracted from an OAuth provider's user info endpoint.
11#[derive(Debug, Clone)]
12pub struct OAuthUserInfo {
13    pub id: String,
14    pub email: String,
15    pub name: Option<String>,
16    pub image: Option<String>,
17    pub email_verified: bool,
18}
19
20/// Configuration for a single OAuth provider.
21#[derive(Debug, Clone)]
22pub struct OAuthProvider {
23    pub client_id: String,
24    pub client_secret: String,
25    pub auth_url: String,
26    pub token_url: String,
27    pub user_info_url: String,
28    pub scopes: Vec<String>,
29    pub map_user_info: fn(Value) -> Result<OAuthUserInfo, String>,
30}
31
32impl OAuthProvider {
33    pub fn google(client_id: &str, client_secret: &str) -> Self {
34        Self {
35            client_id: client_id.to_string(),
36            client_secret: client_secret.to_string(),
37            auth_url: "https://accounts.google.com/o/oauth2/v2/auth".to_string(),
38            token_url: "https://oauth2.googleapis.com/token".to_string(),
39            user_info_url: "https://www.googleapis.com/oauth2/v3/userinfo".to_string(),
40            scopes: vec![
41                "openid".to_string(),
42                "email".to_string(),
43                "profile".to_string(),
44            ],
45            map_user_info: |v| {
46                Ok(OAuthUserInfo {
47                    id: v["sub"].as_str().ok_or("missing sub")?.to_string(),
48                    email: v["email"].as_str().ok_or("missing email")?.to_string(),
49                    name: v["name"].as_str().map(String::from),
50                    image: v["picture"].as_str().map(String::from),
51                    email_verified: v["email_verified"].as_bool().unwrap_or(false),
52                })
53            },
54        }
55    }
56
57    pub fn github(client_id: &str, client_secret: &str) -> Self {
58        Self {
59            client_id: client_id.to_string(),
60            client_secret: client_secret.to_string(),
61            auth_url: "https://github.com/login/oauth/authorize".to_string(),
62            token_url: "https://github.com/login/oauth/access_token".to_string(),
63            user_info_url: "https://api.github.com/user".to_string(),
64            scopes: vec!["user:email".to_string()],
65            map_user_info: |v| {
66                Ok(OAuthUserInfo {
67                    id: v["id"]
68                        .as_i64()
69                        .map(|i| i.to_string())
70                        .or_else(|| v["id"].as_str().map(String::from))
71                        .ok_or("missing id")?,
72                    email: v["email"].as_str().ok_or("missing email")?.to_string(),
73                    name: v["name"].as_str().map(String::from),
74                    image: v["avatar_url"].as_str().map(String::from),
75                    email_verified: true,
76                })
77            },
78        }
79    }
80
81    pub fn discord(client_id: &str, client_secret: &str) -> Self {
82        Self {
83            client_id: client_id.to_string(),
84            client_secret: client_secret.to_string(),
85            auth_url: "https://discord.com/api/oauth2/authorize".to_string(),
86            token_url: "https://discord.com/api/oauth2/token".to_string(),
87            user_info_url: "https://discord.com/api/users/@me".to_string(),
88            scopes: vec!["identify".to_string(), "email".to_string()],
89            map_user_info: |v| {
90                Ok(OAuthUserInfo {
91                    id: v["id"].as_str().ok_or("missing id")?.to_string(),
92                    email: v["email"].as_str().ok_or("missing email")?.to_string(),
93                    name: v["username"].as_str().map(String::from),
94                    image: v["avatar"].as_str().map(|a| {
95                        format!(
96                            "https://cdn.discordapp.com/avatars/{}/{}.png",
97                            v["id"].as_str().unwrap_or(""),
98                            a
99                        )
100                    }),
101                    email_verified: v["verified"].as_bool().unwrap_or(false),
102                })
103            },
104        }
105    }
106}