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}