mas_oidc_client/requests/
userinfo.rs1use std::collections::HashMap;
12
13use headers::{ContentType, HeaderMapExt, HeaderValue};
14use http::header::ACCEPT;
15use mas_http::RequestBuilderExt;
16use mime::Mime;
17use serde_json::Value;
18use url::Url;
19
20use super::jose::JwtVerificationData;
21use crate::{
22    error::{IdTokenError, ResponseExt, UserInfoError},
23    requests::jose::verify_signed_jwt,
24};
25
26#[tracing::instrument(skip_all, fields(userinfo_endpoint))]
55pub async fn fetch_userinfo(
56    http_client: &reqwest::Client,
57    userinfo_endpoint: &Url,
58    access_token: &str,
59    jwt_verification_data: Option<JwtVerificationData<'_>>,
60) -> Result<HashMap<String, Value>, UserInfoError> {
61    tracing::debug!("Obtaining user info…");
62
63    let expected_content_type = if jwt_verification_data.is_some() {
64        "application/jwt"
65    } else {
66        mime::APPLICATION_JSON.as_ref()
67    };
68
69    let userinfo_request = http_client
70        .get(userinfo_endpoint.as_str())
71        .bearer_auth(access_token)
72        .header(ACCEPT, HeaderValue::from_static(expected_content_type));
73
74    let userinfo_response = userinfo_request
75        .send_traced()
76        .await?
77        .error_from_oauth2_error_response()
78        .await?;
79
80    let content_type: Mime = userinfo_response
81        .headers()
82        .typed_try_get::<ContentType>()
83        .map_err(|_| UserInfoError::InvalidResponseContentTypeValue)?
84        .ok_or(UserInfoError::MissingResponseContentType)?
85        .into();
86
87    if content_type.essence_str() != expected_content_type {
88        return Err(UserInfoError::UnexpectedResponseContentType {
89            expected: expected_content_type.to_owned(),
90            got: content_type.to_string(),
91        });
92    }
93
94    let claims = if let Some(verification_data) = jwt_verification_data {
95        let response_body = userinfo_response.text().await?;
96        verify_signed_jwt(&response_body, verification_data)
97            .map_err(IdTokenError::from)?
98            .into_parts()
99            .1
100    } else {
101        userinfo_response.json().await?
102    };
103
104    Ok(claims)
105}