use crate::protocol::{AcmeError, Request, Response, Url};
use crate::schema;
use crate::schema::authorizations::AuthorizationStatus;
use crate::schema::challenges::ChallengeStatus;
use crate::schema::{
authorizations::Authorization as AuthorizationSchema,
challenges::{
Challenge as ChallengeSchema, ChallengeKind, ChallengeReadyRequest, Dns01Challenge,
Http01Challenge,
},
Identifier,
};
use super::{account::Account, client::Client, order::Order};
#[derive(Debug)]
pub struct Authorization<'o, K> {
order: &'o Order<'o, K>,
data: schema::authorizations::Authorization,
url: Url,
}
impl<'o, K> Authorization<'o, K> {
pub(crate) fn new(
order: &'o Order<'o, K>,
data: schema::authorizations::Authorization,
url: Url,
) -> Self {
Self { order, data, url }
}
#[inline]
pub(crate) fn client(&self) -> &Client {
self.order.client()
}
#[inline]
pub(crate) fn account(&self) -> &Account<K> {
self.order.account()
}
pub fn data(&self) -> &schema::authorizations::Authorization {
&self.data
}
pub fn url(&self) -> &Url {
&self.url
}
pub fn identifier(&self) -> &Identifier {
&self.data.identifier
}
pub fn challenge<'c: 'o>(&'c self, kind: &ChallengeKind) -> Option<Challenge<'o, 'c, K>> {
for chall in &self.data().challenges {
if chall.kind() == *kind {
let url = chall.url().unwrap();
return Some(Challenge::new(self, chall.clone(), url));
}
}
None
}
pub async fn refresh(&mut self) -> Result<(), AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
let response: Response<schema::authorizations::Authorization> = self
.client()
.execute(Request::get(
self.url().clone(),
self.account().request_key(),
))
.await?;
self.data = response.into_inner();
Ok(())
}
#[tracing::instrument(skip(self), level = "debug", fields(identifier = %self.data().identifier))]
pub async fn finalize(&mut self) -> Result<(), AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
tracing::debug!("Polling authorization resource to check for status updates");
loop {
tracing::trace!("Fetching authorization to check status");
let info: Response<AuthorizationSchema> = self
.client()
.execute(Request::get(
self.url().clone(),
self.account().request_key(),
))
.await?;
let delay = info
.retry_after()
.unwrap_or_else(|| std::time::Duration::from_secs(1));
self.data = info.into_inner();
if self.data().status.is_finished() {
tracing::debug!(status=?self.data().status, "Authorization is finished");
break;
}
tracing::trace!(status=?self.data().status, delay=?delay, "Authorization is not finished");
tokio::time::sleep(delay).await;
}
if !self.data().status.is_valid() {
if let Some(error) = self
.data()
.challenges
.iter()
.filter_map(|c| c.error())
.next()
{
tracing::error!("Authorization failed with challenge error: {:?}", error);
return Err(AcmeError::from(error.clone()));
};
return Err(AcmeError::AuthorizationError(format!(
"{:?}",
self.data().status
)));
}
Ok(())
}
}
#[derive(Debug)]
pub struct Challenge<'a, 'c, K> {
auth: &'c Authorization<'a, K>,
data: schema::challenges::Challenge,
url: Url,
}
impl<'a, 'c: 'a, K> Challenge<'a, 'c, K> {
pub(crate) fn new(
auth: &'a Authorization<'a, K>,
data: schema::challenges::Challenge,
url: Url,
) -> Self {
Self { auth, data, url }
}
#[inline]
pub(crate) fn client(&self) -> &Client {
self.auth.client()
}
#[inline]
pub(crate) fn account(&self) -> &Account<K> {
self.auth.account()
}
pub fn data(&self) -> &schema::challenges::Challenge {
&self.data
}
pub fn http01(&self) -> Option<Http01Challenge> {
self.data().http01().cloned()
}
pub fn dns01(&self) -> Option<Dns01Challenge> {
self.data().dns01().cloned()
}
pub fn url(&self) -> &Url {
&self.url
}
pub async fn ready(&mut self) -> Result<(), AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
let name = self.data().name().unwrap_or("<unknown>");
tracing::trace!("POST to notify that challenge {} is ready", name);
let request = Request::post(
ChallengeReadyRequest,
self.url().clone(),
self.account().request_key(),
);
let response = self
.client()
.execute::<_, _, ChallengeSchema>(request)
.await?;
self.data = response.into_inner();
tracing::debug!("Notified that challenge {} is ready", name);
Ok(())
}
pub async fn finalize(&mut self) -> Result<(), AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
tracing::debug!("Polling authorization resource to check for status updates");
let name = self
.data()
.name()
.ok_or_else(|| AcmeError::UnknownChallenge("Unknown".into()))?
.to_owned();
let kind = self.data().kind();
loop {
tracing::trace!("Fetching authorization to check status");
let info: Response<AuthorizationSchema> = self
.client()
.execute(Request::get(
self.auth.url().clone(),
self.account().request_key(),
))
.await?;
let delay = info
.retry_after()
.unwrap_or_else(|| std::time::Duration::from_secs(1));
let chall = self
.auth
.challenge(&kind)
.ok_or_else(|| AcmeError::MissingData("challenge"))?
.data;
let auth = info.into_inner();
tracing::trace!("Checking challenge {name}");
match chall.status() {
Some(ChallengeStatus::Invalid) => {
tracing::warn!(status=?chall.status(), "Challenge {name} is invalid");
if let Some(error) = chall.error() {
return Err(AcmeError::from(error.clone()));
}
return Err(AcmeError::NotReady("challenge"));
}
Some(ChallengeStatus::Valid) => {
tracing::debug!("Completed challenge {name}");
break;
}
None => {
tracing::warn!("Unable to get status for challenge {name}");
}
_ => {}
}
if matches!(auth.status, AuthorizationStatus::Valid) {
tracing::warn!(status=?auth.status, "Authorization is finished.\nThe challenge is not marked as complete\nMaybe this challenge is orphaned, or the authorization has expired. Either way, not waiting any longer");
break;
} else if auth.status.is_finished() {
tracing::warn!(status=?auth.status, "Authorization is finished.\n Maybe this challenge is orphaned, or the authorization has expired. Either way, not waiting any longer");
break;
}
tracing::trace!(auth_status=?auth.status, challenge_status=?chall.status(), delay=?delay, "Authorization is not finished");
tokio::time::sleep(delay).await;
}
Ok(())
}
}