auth dns over atproto
at main 103 lines 3.2 kB view raw
1use std::sync::Arc; 2use std::time::Instant; 3 4use axum::{Json, Router, extract::State, http::StatusCode, routing::post}; 5use onis_common::metrics::PrometheusHandle; 6use serde::{Deserialize, Serialize}; 7 8use crate::checker::Checker; 9use crate::client::AppviewClient; 10 11/// Shared state for the verification HTTP API. 12pub struct VerifyState { 13 /// DNS delegation checker used for on-demand and scheduled verification. 14 pub checker: Checker, 15 /// HTTP client for communicating verification results to the appview. 16 pub client: AppviewClient, 17 /// Prometheus metrics handle for the `/metrics` endpoint. 18 pub metrics_handle: PrometheusHandle, 19} 20 21pub fn router(state: Arc<VerifyState>) -> Router { 22 let metrics_router = onis_common::metrics::router(state.metrics_handle.clone()); 23 24 Router::new() 25 .route("/verify", post(verify)) 26 .with_state(state) 27 .merge(metrics_router) 28} 29 30/// Request body for the `POST /verify` endpoint. 31#[derive(Deserialize)] 32struct VerifyRequest { 33 /// Domain name of the zone to verify. 34 zone: String, 35 /// DID of the zone owner. 36 did: String, 37} 38 39/// Response body returned by the `POST /verify` endpoint. 40#[derive(Serialize)] 41struct VerifyResponse { 42 /// Domain name of the zone that was checked. 43 zone: String, 44 /// Whether the zone passed delegation verification. 45 verified: bool, 46 /// NS records found for the zone during the check. 47 ns_records: Vec<String>, 48 /// Whether a matching TXT proof record was found. 49 txt_match: bool, 50} 51 52async fn verify( 53 State(state): State<Arc<VerifyState>>, 54 Json(body): Json<VerifyRequest>, 55) -> Result<Json<VerifyResponse>, StatusCode> { 56 let start = Instant::now(); 57 58 let result = state 59 .checker 60 .check_zone(&body.zone, &body.did) 61 .await 62 .map_err(|e| { 63 tracing::error!(zone = %body.zone, error = %e, "on-demand verification failed"); 64 metrics::counter!("verification_checks_total", "result" => "error").increment(1); 65 metrics::histogram!("verification_check_duration_seconds") 66 .record(start.elapsed().as_secs_f64()); 67 StatusCode::INTERNAL_SERVER_ERROR 68 })?; 69 70 let result_label = if result.verified { "verified" } else { "unverified" }; 71 metrics::counter!("verification_checks_total", "result" => result_label).increment(1); 72 metrics::histogram!("verification_check_duration_seconds") 73 .record(start.elapsed().as_secs_f64()); 74 75 if let Err(e) = state 76 .client 77 .set_verification(&body.zone, &body.did, result.verified) 78 .await 79 { 80 tracing::error!( 81 zone = %body.zone, 82 did = %body.did, 83 error = %e, 84 "failed to update verification status" 85 ); 86 return Err(StatusCode::INTERNAL_SERVER_ERROR); 87 } 88 89 tracing::info!( 90 zone = %body.zone, 91 did = %body.did, 92 verified = result.verified, 93 ns = ?result.ns_records, 94 "on-demand verification complete" 95 ); 96 97 Ok(Json(VerifyResponse { 98 zone: body.zone, 99 verified: result.verified, 100 ns_records: result.ns_records, 101 txt_match: result.txt_match, 102 })) 103}