Skip to main content

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