light_openid/
client.rs

1//! # Open ID client implementation
2
3use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
4use base64::Engine;
5use std::collections::HashMap;
6use std::error::Error;
7
8use crate::primitives::{OpenIDConfig, OpenIDTokenResponse, OpenIDUserInfo};
9
10impl OpenIDConfig {
11    /// Load OpenID configuration from a given .well-known/openid-configuration URL
12    pub async fn load_from_url(url: &str) -> Result<Self, Box<dyn Error>> {
13        Ok(reqwest::get(url).await?.json().await?)
14    }
15
16    /// Get the authorization URL where a user should be redirect to perform authentication
17    pub fn gen_authorization_url(
18        &self,
19        client_id: &str,
20        state: &str,
21        redirect_uri: &str,
22    ) -> String {
23        let client_id = urlencoding::encode(client_id);
24        let state = urlencoding::encode(state);
25        let redirect_uri = urlencoding::encode(redirect_uri);
26
27        format!("{}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={redirect_uri}", self.authorization_endpoint)
28    }
29
30    /// Query the token endpoint
31    ///
32    /// This endpoint returns both the parsed and the raw response, to allow handling
33    /// of bonus fields
34    pub async fn request_token(
35        &self,
36        client_id: &str,
37        client_secret: &str,
38        code: &str,
39        redirect_uri: &str,
40    ) -> Result<(OpenIDTokenResponse, String), Box<dyn Error>> {
41        let authorization = BASE64_STANDARD.encode(format!("{}:{}", client_id, client_secret));
42
43        let mut params = HashMap::new();
44        params.insert("grant_type", "authorization_code");
45        params.insert("code", code);
46        params.insert("redirect_uri", redirect_uri);
47
48        let response = reqwest::Client::new()
49            .post(&self.token_endpoint)
50            .header("Authorization", format!("Basic {authorization}"))
51            .form(&params)
52            .send()
53            .await?
54            .text()
55            .await?;
56
57        Ok((serde_json::from_str(&response)?, response))
58    }
59
60    /// Query the UserInfo endpoint.
61    ///
62    /// This endpoint should be use after having successfully retrieved the token
63    ///
64    /// This endpoint returns both the parsed value and the raw response, in case of presence
65    /// of additional fields
66    pub async fn request_user_info(
67        &self,
68        token: &OpenIDTokenResponse,
69    ) -> Result<(OpenIDUserInfo, String), Box<dyn Error>> {
70        let response = reqwest::Client::new()
71            .get(self.userinfo_endpoint.as_ref().expect(
72                "This client only support information retrieval through userinfo endpoint!",
73            ))
74            .header("Authorization", format!("Bearer {}", token.access_token))
75            .send()
76            .await?
77            .text()
78            .await?;
79
80        Ok((serde_json::from_str(&response)?, response))
81    }
82}