use std::collections::HashMap;
use headers::{ContentType, HeaderMapExt, HeaderValue};
use http::header::ACCEPT;
use mas_http::RequestBuilderExt;
use mas_jose::claims;
use mime::Mime;
use serde_json::Value;
use url::Url;
use super::jose::JwtVerificationData;
use crate::{
error::{IdTokenError, ResponseExt, UserInfoError},
requests::jose::verify_signed_jwt,
types::IdToken,
};
#[tracing::instrument(skip_all, fields(userinfo_endpoint))]
pub async fn fetch_userinfo(
http_client: &reqwest::Client,
userinfo_endpoint: &Url,
access_token: &str,
jwt_verification_data: Option<JwtVerificationData<'_>>,
auth_id_token: &IdToken<'_>,
) -> Result<HashMap<String, Value>, UserInfoError> {
tracing::debug!("Obtaining user info…");
let expected_content_type = if jwt_verification_data.is_some() {
"application/jwt"
} else {
mime::APPLICATION_JSON.as_ref()
};
let userinfo_request = http_client
.get(userinfo_endpoint.as_str())
.bearer_auth(access_token)
.header(ACCEPT, HeaderValue::from_static(expected_content_type));
let userinfo_response = userinfo_request
.send_traced()
.await?
.error_from_oauth2_error_response()
.await?;
let content_type: Mime = userinfo_response
.headers()
.typed_try_get::<ContentType>()
.map_err(|_| UserInfoError::InvalidResponseContentTypeValue)?
.ok_or(UserInfoError::MissingResponseContentType)?
.into();
if content_type.essence_str() != expected_content_type {
return Err(UserInfoError::UnexpectedResponseContentType {
expected: expected_content_type.to_owned(),
got: content_type.to_string(),
});
}
let mut claims = if let Some(verification_data) = jwt_verification_data {
let response_body = userinfo_response.text().await?;
verify_signed_jwt(&response_body, verification_data)
.map_err(IdTokenError::from)?
.into_parts()
.1
} else {
userinfo_response.json().await?
};
let mut auth_claims = auth_id_token.payload().clone();
let sub = claims::SUB
.extract_required(&mut claims)
.map_err(IdTokenError::from)?;
let auth_sub = claims::SUB
.extract_required(&mut auth_claims)
.map_err(IdTokenError::from)?;
if sub != auth_sub {
return Err(IdTokenError::WrongSubjectIdentifier.into());
}
Ok(claims)
}