use std::collections::HashMap;
use std::time::Duration;
use keyhog_core::VerificationResult;
use reqwest::Client;
use crate::interpolate::interpolate;
use crate::verify::credential::verification_timeout;
use crate::verify::{
body_indicates_error, build_request_for_step, evaluate_success, execute_request,
extract_metadata, read_response_body, resolved_client_for_url, RequestBuildResult,
VerificationAttempt,
};
pub(crate) async fn verify_multi_step(
client: &Client,
spec: &keyhog_core::VerifySpec,
credential: &str,
companions: &HashMap<String, String>,
timeout: Duration,
allow_private_ips: bool,
allow_http: bool,
) -> VerificationAttempt {
let mut all_metadata = HashMap::new();
let mut current_companions = companions.clone();
let mut last_result = VerificationResult::Unverifiable;
for step in &spec.steps {
let step_timeout = verification_timeout(spec, timeout);
let raw_url = interpolate(&step.url, credential, ¤t_companions);
if let Err(reason) = crate::domain_allowlist::check_url_against_spec(&raw_url, spec) {
return VerificationAttempt {
result: VerificationResult::Error(reason),
metadata: all_metadata,
transient: false,
};
}
let resolved_target = match resolved_client_for_url(
client,
&raw_url,
step_timeout,
allow_private_ips,
allow_http,
)
.await
{
Ok(resolved_target) => resolved_target,
Err(result) => {
return VerificationAttempt {
result,
metadata: all_metadata,
transient: false,
};
}
};
let base_request = build_request_for_step(
&resolved_target.client,
&step.method,
&step.auth,
resolved_target.url.clone(),
credential,
¤t_companions,
step_timeout,
)
.await;
let mut request = match base_request {
RequestBuildResult::Ready(request) => request,
RequestBuildResult::Final {
result,
metadata,
transient,
} => {
all_metadata.extend(metadata);
return VerificationAttempt {
result,
metadata: all_metadata,
transient,
};
}
};
for header in &step.headers {
let value = interpolate(&header.value, credential, ¤t_companions);
request = request.header(&header.name, &value);
}
if let Some(body_template) = &step.body {
let body = interpolate(body_template, credential, ¤t_companions);
request = request.body(body);
}
let service = step.auth.service_name().unwrap_or("unknown");
crate::rate_limit::get_rate_limiter().wait(service).await;
let response = match execute_request(request).await {
Ok(resp) => resp,
Err(error) => {
return VerificationAttempt {
result: error.result,
metadata: all_metadata,
transient: error.transient,
};
}
};
let status = response.status().as_u16();
let body = match read_response_body(response).await {
Ok(body) => body,
Err(error) => {
return VerificationAttempt {
result: error.result,
metadata: all_metadata,
transient: error.transient,
};
}
};
if status == 429 {
crate::rate_limit::get_rate_limiter()
.update_limit(service, 0.5)
.await;
return VerificationAttempt {
result: VerificationResult::RateLimited,
metadata: all_metadata,
transient: true,
};
}
if !evaluate_success(&step.success, status, &body) || body_indicates_error(&body) {
return VerificationAttempt {
result: VerificationResult::Dead,
metadata: all_metadata,
transient: false,
};
}
let step_metadata = extract_metadata(&step.extract, &body);
for (k, v) in &step_metadata {
current_companions.insert(format!("{}.{}", step.name, k), v.clone());
}
all_metadata.extend(step_metadata);
last_result = VerificationResult::Live;
}
VerificationAttempt {
result: last_result,
metadata: all_metadata,
transient: false,
}
}