use serde::Deserialize;
use serde_json::Value;
use crate::signer::SignerError;
use super::client::UpstreamError;
#[derive(Debug, Deserialize)]
pub(super) struct UpstreamOrderView {
pub(super) status: String,
#[serde(default)]
pub(super) authorizations: Vec<String>,
#[serde(default)]
pub(super) finalize: Option<String>,
#[serde(default)]
pub(super) certificate: Option<String>,
#[serde(default)]
pub(super) error: Option<Value>,
}
#[derive(Debug, Deserialize)]
pub(super) struct RenewalInfoView {
#[serde(rename = "suggestedWindow")]
pub(super) suggested_window: SuggestedWindow,
#[serde(rename = "explanationURL", default)]
pub(super) explanation_url: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(super) struct SuggestedWindow {
pub(super) start: String,
pub(super) end: String,
}
pub(super) fn parse_rfc3339(value: &str) -> Result<i64, SignerError> {
time::OffsetDateTime::parse(value, &time::format_description::well_known::Rfc3339)
.map(|parsed| parsed.unix_timestamp())
.map_err(|error| {
SignerError::Internal(format!("upstream ARI window was not RFC3339: {error}"))
})
}
pub(super) fn upstream_to_signer_error(error: UpstreamError) -> SignerError {
if error.is_bad_csr() {
SignerError::BadCsr
} else {
SignerError::Internal(error.to_string())
}
}
#[derive(Debug, Deserialize)]
pub(super) struct UpstreamAuthzView {
pub(super) status: String,
pub(super) identifier: UpstreamIdentifier,
#[serde(default)]
pub(super) challenges: Vec<UpstreamChallengeView>,
}
#[derive(Debug, Deserialize)]
pub(super) struct UpstreamIdentifier {
pub(super) value: String,
}
#[derive(Debug, Deserialize)]
pub(super) struct UpstreamChallengeView {
#[serde(rename = "type")]
pub(super) typ: String,
pub(super) url: String,
#[serde(default)]
pub(super) token: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_tokenless_challenge_does_not_fail_the_authorization() {
let body = serde_json::json!({
"status": "pending",
"identifier": { "type": "dns", "value": "example.com" },
"challenges": [
{
"type": "dns-persist-01",
"url": "https://ca.example/chall/persist",
"status": "pending",
"accounturi": "https://ca.example/acct/1",
},
{
"type": "dns-01",
"url": "https://ca.example/chall/dns",
"status": "pending",
"token": "the-token",
},
],
});
let view: UpstreamAuthzView = serde_json::from_value(body).expect("authorization parses");
assert_eq!(view.identifier.value, "example.com");
assert_eq!(view.challenges.len(), 2);
assert_eq!(view.challenges[0].token, None);
assert_eq!(
view.challenges[1].token.as_deref(),
Some("the-token"),
"the challenge the relay answers still carries its token"
);
}
#[test]
fn a_challenge_missing_its_url_is_still_a_parse_failure() {
let body = serde_json::json!({
"status": "pending",
"identifier": { "type": "dns", "value": "example.com" },
"challenges": [{ "type": "dns-01", "token": "the-token" }],
});
serde_json::from_value::<UpstreamAuthzView>(body).unwrap_err();
}
}