Something went wrong. Try again.
Identities for entities did.bot
agent llm did
Something went wrong. Try again.
4.6 kB · 124 lines
Rust
at commit 18ba4fe0
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125//! Handing a code to a client that is listening on this machine.//!//! The last step of every authorization this daemon completes is a `GET` of//! a redirect the server built. The server cannot make it: `plan/node.md`//! puts the daemon on the agent host and the authorization server somewhere//! else, and the client's callback listens on loopback here. So the daemon//! fetches it.//!//! That is a request whose target came from a process the model started, so//! it is bounded twice: loopback only, and no chains. Both bounds live here//! live here rather than at the call site, so that a second caller cannot//! acquire a weaker copy of them.
use url::Url;
/// Why a code did not reach its client.#[derive(Debug, thiserror::Error)]pub enum Trouble { /// The redirect pointed somewhere that is not this machine. #[error("{0}")] Elsewhere(String), /// The client did not answer, or the URL was not one. #[error("{0}")] Failed(String),}
/// Whether a URL names this machine and nowhere else.////// `localhost` by name, or any address the standard library calls a loopback/// address — which covers `127.0.0.0/8` and `::1` without this having to/// enumerate either.////// The host is taken from [`Url::host`] rather than [`Url::host_str`]. The/// string form of an IPv6 host keeps the brackets the URL syntax requires,/// `[::1]` parses as no address at all, and a check written against it/// silently refuses to deliver to a client listening on IPv6 loopback.pub fn is_loopback(target: &Url) -> bool { match target.host() { Some(url::Host::Domain(name)) => name.eq_ignore_ascii_case("localhost"), Some(url::Host::Ipv4(address)) => address.is_loopback(), Some(url::Host::Ipv6(address)) => address.is_loopback(), None => false, }}
/// Fetches `redirect`, which is the act of delivering the code in it.////// An error names the target without its query, which carries the code.////// `http` is expected to be built with redirects turned off; this does not/// build its own client, because the callers already hold one and a second/// would be a second set of timeouts to keep in step.pub async fn deliver(http: &reqwest::Client, redirect: &str) -> Result<(), Trouble> { let target = Url::parse(redirect) .map_err(|err| Trouble::Failed(format!("the redirect is unusable: {err}")))?; if !is_loopback(&target) { let mut shown = target; shown.set_query(None); shown.set_fragment(None); return Err(Trouble::Elsewhere(format!( "the client asked for its code at {shown}, which is not loopback" ))); } http.get(target).send().await.map_err(|err| { Trouble::Failed(format!( "the client did not take its code: {}", err.without_url() )) })?; Ok(())}
#[cfg(test)]mod tests { use super::*;
fn loopback(url: &str) -> bool { is_loopback(&Url::parse(url).unwrap()) }
#[test] fn this_machine_by_name_or_by_number() { assert!(loopback("http://localhost:40831/cb")); assert!(loopback("http://LOCALHOST:40831/cb")); assert!(loopback("http://127.0.0.1:40831/cb")); // The whole 127/8 block, not just the one address everybody types. assert!(loopback("http://127.9.9.9/cb")); assert!(loopback("http://[::1]:40831/cb")); }
#[test] fn anywhere_else_is_not_delivered_to() { assert!(!loopback("https://example.invalid/cb")); // A host that merely reads like this machine is somebody else's. assert!(!loopback("http://localhost.example.invalid/cb")); assert!(!loopback("http://10.0.0.1/cb")); }
/// Both refusals are logged, so neither may carry the code. #[tokio::test] async fn a_refusal_does_not_repeat_the_code() { let http = didbot_http::client(); let elsewhere = deliver(&http, "https://example.invalid/cb?code=do-not-log") .await .unwrap_err() .to_string(); assert!(!elsewhere.contains("do-not-log"), "{elsewhere}");
// A loopback port nobody is listening on. let port = std::net::TcpListener::bind("127.0.0.1:0") .unwrap() .local_addr() .unwrap() .port(); let unanswered = deliver( &http, &format!("http://127.0.0.1:{port}/cb?code=do-not-log"), ) .await .unwrap_err() .to_string(); assert!(!unanswered.contains("do-not-log"), "{unanswered}"); }}