//! # Open ID client implementation use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; use base64::Engine; use std::collections::HashMap; use std::error::Error; use crate::primitives::{OpenIDConfig, OpenIDTokenResponse, OpenIDUserInfo}; impl OpenIDConfig { /// Load OpenID configuration from a given .well-known/openid-configuration URL pub async fn load_from_url(url: &str) -> Result> { Ok(reqwest::get(url).await?.json().await?) } /// Get the authorization URL where a user should be redirect to perform authentication pub fn gen_authorization_url( &self, client_id: &str, state: &str, redirect_uri: &str, ) -> String { let client_id = urlencoding::encode(client_id); let state = urlencoding::encode(state); let redirect_uri = urlencoding::encode(redirect_uri); format!("{}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={redirect_uri}", self.authorization_endpoint) } /// Query the token endpoint /// /// This endpoint returns both the parsed and the raw response, to allow handling /// of bonus fields pub async fn request_token( &self, client_id: &str, client_secret: &str, code: &str, redirect_uri: &str, ) -> Result<(OpenIDTokenResponse, String), Box> { let authorization = BASE64_STANDARD.encode(format!("{}:{}", client_id, client_secret)); let mut params = HashMap::new(); params.insert("grant_type", "authorization_code"); params.insert("code", code); params.insert("redirect_uri", redirect_uri); let response = reqwest::Client::new() .post(&self.token_endpoint) .header("Authorization", format!("Basic {authorization}")) .form(¶ms) .send() .await? .text() .await?; Ok((serde_json::from_str(&response)?, response)) } /// Query the UserInfo endpoint. /// /// This endpoint should be use after having successfully retrieved the token /// /// This endpoint returns both the parsed value and the raw response, in case of presence /// of additional fields pub async fn request_user_info( &self, token: &OpenIDTokenResponse, ) -> Result<(OpenIDUserInfo, String), Box> { let response = reqwest::Client::new() .get(self.userinfo_endpoint.as_ref().expect( "This client only support information retrieval through userinfo endpoint!", )) .header("Authorization", format!("Bearer {}", token.access_token)) .send() .await? .text() .await?; Ok((serde_json::from_str(&response)?, response)) } }