use chrono::{DateTime, Utc};
use mas_jose::claims::{self, TokenHash};
use oauth2_types::{
requests::{AccessTokenRequest, AccessTokenResponse, RefreshTokenGrant},
scope::Scope,
};
use rand::Rng;
use url::Url;
use super::jose::JwtVerificationData;
use crate::{
error::{IdTokenError, TokenRefreshError},
http_service::HttpService,
requests::{jose::verify_id_token, token::request_access_token},
types::{client_credentials::ClientCredentials, IdToken},
};
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip_all, fields(token_endpoint))]
pub async fn refresh_access_token(
http_service: &HttpService,
client_credentials: ClientCredentials,
token_endpoint: &Url,
refresh_token: String,
scope: Option<Scope>,
id_token_verification_data: Option<JwtVerificationData<'_>>,
auth_id_token: Option<&IdToken<'_>>,
now: DateTime<Utc>,
rng: &mut impl Rng,
) -> Result<(AccessTokenResponse, Option<IdToken<'static>>), TokenRefreshError> {
tracing::debug!("Refreshing access token…");
let token_response = request_access_token(
http_service,
client_credentials,
token_endpoint,
AccessTokenRequest::RefreshToken(RefreshTokenGrant {
refresh_token,
scope,
}),
now,
rng,
)
.await?;
let id_token = if let Some((verification_data, id_token)) =
id_token_verification_data.zip(token_response.id_token.as_ref())
{
let auth_id_token = auth_id_token.ok_or(IdTokenError::MissingAuthIdToken)?;
let signing_alg = verification_data.signing_algorithm;
let id_token = verify_id_token(id_token, verification_data, Some(auth_id_token), now)?;
let mut claims = id_token.payload().clone();
claims::AT_HASH
.extract_optional_with_options(
&mut claims,
TokenHash::new(signing_alg, &token_response.access_token),
)
.map_err(IdTokenError::from)?;
Some(id_token.into_owned())
} else {
None
};
Ok((token_response, id_token))
}