async_google_auth/
lib.rs

1use oauth2::basic::BasicClient;
2use oauth2::reqwest::async_http_client;
3use oauth2::{
4    AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, RedirectUrl, Scope,
5    TokenResponse, TokenUrl,
6};
7use reqwest::Client;
8use serde::{Deserialize, Serialize};
9use std::error::Error;
10
11pub struct Google {
12    client: BasicClient,
13}
14
15#[derive(Deserialize, Serialize, Debug)]
16pub struct UserInfo {
17    #[serde(rename = "sub")]
18    pub open_id: String,
19
20    #[serde(rename = "name")]
21    pub username: String,
22    given_name: Option<String>,
23    family_name: Option<String>,
24
25    #[serde(rename = "picture")]
26    pub profile_url: String,
27
28    pub email: String,
29    pub email_verified: bool,
30    locale: Option<String>,
31}
32
33impl Google {
34    /// Creates a new instance of the Google authorization client.
35    ///
36    /// # Arguments
37    ///
38    /// * `appid` - The client ID provided by Google when registering the application.
39    /// * `app_secret` - The client secret provided by Google when registering the
40    ///   application.
41    /// * `callback_url` - The URL that the user will be redirected to after authorization
42    ///   is complete. This URL should be an endpoint in the application that will handle
43    ///   the authorization code.
44    ///
45    /// # Returns
46    ///
47    /// * `Google` - A new instance of the Google authorization client.
48    pub fn new(appid: String, app_secret: String, callback_url: String) -> Google {
49        let client_id = ClientId::new(appid.clone());
50        let client_secret = ClientSecret::new(app_secret.clone());
51
52        let auth_url =
53            AuthUrl::new("https://accounts.google.com/o/oauth2/auth".to_string()).unwrap();
54        let token_url =
55            TokenUrl::new("https://accounts.google.com/o/oauth2/token".to_string()).unwrap();
56
57        let redirect_url = RedirectUrl::new(callback_url.clone()).unwrap();
58
59        let client = BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url))
60            .set_redirect_uri(redirect_url);
61
62        Google { client }
63    }
64
65    /// Generates a URL that the user should be redirected to in order to authorize this
66    /// application. This URL is the standard authorization URL for the OAuth2 flow with the
67    /// Google OAuth2 provider, and includes the scopes required to fetch the user's profile
68    /// information.
69    pub fn get_redirect_url(&self) -> String {
70        let (auth_url, _csrf_token) = self
71            .client
72            .authorize_url(CsrfToken::new_random)
73            .add_scope(Scope::new("openid".to_string()))
74            .add_scope(Scope::new("email".to_string()))
75            .add_scope(Scope::new("profile".to_string()))
76            .url();
77
78        auth_url.to_string()
79    }
80
81    /// Fetches and returns the user's profile information from Google using the provided
82    /// authorization code.
83    ///
84    /// This function exchanges the provided authorization code for an access token and then
85    /// uses that token to request the user's profile information from Google's userinfo
86    /// endpoint. The user's information is returned as a `UserInfo` struct.
87    ///
88    /// # Arguments
89    ///
90    /// * `code` - A `String` representing the authorization code received from Google's
91    ///            OAuth2 authorization flow.
92    ///
93    /// # Returns
94    ///
95    /// * `Result<UserInfo, Box<dyn Error>>` - On success, returns `Ok(UserInfo)` containing
96    ///   the user's profile information. On failure, returns `Err` with an error describing
97    ///   what went wrong.
98    ///
99    /// # Errors
100    ///
101    /// This function can return an error if the authorization code exchange fails, if the
102    /// request to fetch the user's profile information fails, or if parsing the response
103    /// into a `UserInfo` struct fails.
104    pub async fn get_userinfo(&self, code: String) -> Result<UserInfo, Box<dyn Error>> {
105        let token = match self
106            .client
107            .exchange_code(AuthorizationCode::new(code))
108            .request_async(async_http_client)
109            .await
110        {
111            Ok(result) => result.access_token().clone(),
112            Err(err) => {
113                return Err(err.into());
114            }
115        };
116
117        let response = Client::new()
118            .get("https://www.googleapis.com/oauth2/v3/userinfo".to_string())
119            .bearer_auth(&token.secret())
120            .send()
121            .await?;
122
123        if !response.status().is_success() {
124            return Err("Failed to fetch profile information".into());
125        }
126
127        let result = match response.json::<UserInfo>().await {
128            Ok(result) => result,
129            Err(err) => {
130                return Err(err.into());
131            }
132        };
133
134        Ok(result)
135    }
136}