use std::sync::Arc; use std::time::Instant; use axum::{Json, Router, extract::State, http::StatusCode, routing::post}; use onis_common::metrics::PrometheusHandle; use serde::{Deserialize, Serialize}; use crate::checker::Checker; use crate::client::AppviewClient; /// Shared state for the verification HTTP API. pub struct VerifyState { /// DNS delegation checker used for on-demand and scheduled verification. pub checker: Checker, /// HTTP client for communicating verification results to the appview. pub client: AppviewClient, /// Prometheus metrics handle for the `/metrics` endpoint. pub metrics_handle: PrometheusHandle, } pub fn router(state: Arc) -> Router { let metrics_router = onis_common::metrics::router(state.metrics_handle.clone()); Router::new() .route("/verify", post(verify)) .with_state(state) .merge(metrics_router) } /// Request body for the `POST /verify` endpoint. #[derive(Deserialize)] struct VerifyRequest { /// Domain name of the zone to verify. zone: String, /// DID of the zone owner. did: String, } /// Response body returned by the `POST /verify` endpoint. #[derive(Serialize)] struct VerifyResponse { /// Domain name of the zone that was checked. zone: String, /// Whether the zone passed delegation verification. verified: bool, /// NS records found for the zone during the check. ns_records: Vec, /// Whether a matching TXT proof record was found. txt_match: bool, } async fn verify( State(state): State>, Json(body): Json, ) -> Result, StatusCode> { let start = Instant::now(); let result = state .checker .check_zone(&body.zone, &body.did) .await .map_err(|e| { tracing::error!(zone = %body.zone, error = %e, "on-demand verification failed"); metrics::counter!("verification_checks_total", "result" => "error").increment(1); metrics::histogram!("verification_check_duration_seconds") .record(start.elapsed().as_secs_f64()); StatusCode::INTERNAL_SERVER_ERROR })?; let result_label = if result.verified { "verified" } else { "unverified" }; metrics::counter!("verification_checks_total", "result" => result_label).increment(1); metrics::histogram!("verification_check_duration_seconds") .record(start.elapsed().as_secs_f64()); if let Err(e) = state .client .set_verification(&body.zone, &body.did, result.verified) .await { tracing::error!( zone = %body.zone, did = %body.did, error = %e, "failed to update verification status" ); return Err(StatusCode::INTERNAL_SERVER_ERROR); } tracing::info!( zone = %body.zone, did = %body.did, verified = result.verified, ns = ?result.ns_records, "on-demand verification complete" ); Ok(Json(VerifyResponse { zone: body.zone, verified: result.verified, ns_records: result.ns_records, txt_match: result.txt_match, })) }