From b0e245e12759e92f72dd1b69ba0b1a24324662c6 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 7 Jan 2023 03:09:58 +1300 Subject: [PATCH] nip-05 related functions --- src/main.rs | 1 + src/nip05.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++ src/overlord/mod.rs | 88 +----------------------- 3 files changed, 163 insertions(+), 86 deletions(-) create mode 100644 src/nip05.rs diff --git a/src/main.rs b/src/main.rs index 0bc62f58..4b157b6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod error; mod feed; mod fetcher; mod globals; +mod nip05; mod overlord; mod people; mod process; diff --git a/src/nip05.rs b/src/nip05.rs new file mode 100644 index 00000000..d62d0637 --- /dev/null +++ b/src/nip05.rs @@ -0,0 +1,160 @@ +use crate::db::{DbPerson, DbPersonRelay, DbRelay}; +use crate::error::Error; +use crate::globals::GLOBALS; +use nostr_types::{Nip05, Unixtime, Url}; + +// This updates the people map and the database with the result +#[allow(dead_code)] +pub async fn validate_nip05(person: DbPerson) -> Result<(), Error> { + let now = Unixtime::now().unwrap(); + + // invalid if their nip-05 is not set + if person.dns_id.is_none() { + GLOBALS + .people + .write() + .await + .upsert_nip05_validity(&person.pubkey, person.dns_id, false, now.0 as u64) + .await?; + return Ok(()); + } + + // Split their DNS ID + let dns_id = person.dns_id.clone().unwrap(); + let (user, domain) = match parse_dns_id(&dns_id) { + Ok(pair) => pair, + Err(_) => { + GLOBALS + .people + .write() + .await + .upsert_nip05_validity(&person.pubkey, person.dns_id, false, now.0 as u64) + .await?; + return Ok(()); + } + }; + + // Fetch NIP-05 + let nip05 = fetch_nip05(&user, &domain).await?; + + // Check if the response matches their public key + match nip05.names.get(&user) { + Some(pk) => { + if pk.as_hex_string() == person.pubkey.0 { + // Validated + GLOBALS + .people + .write() + .await + .upsert_nip05_validity(&person.pubkey, person.dns_id, true, now.0 as u64) + .await?; + } + } + None => { + // Failed + GLOBALS + .people + .write() + .await + .upsert_nip05_validity(&person.pubkey, person.dns_id, false, now.0 as u64) + .await?; + } + } + + Ok(()) +} + +pub async fn get_and_follow_nip05(dns_id: String) -> Result<(), Error> { + // Split their DNS ID + let (user, domain) = parse_dns_id(&dns_id)?; + + // Fetch NIP-05 + let nip05 = fetch_nip05(&user, &domain).await?; + + // Get their pubkey + let pubkey = match nip05.names.get(&user) { + Some(pk) => pk, + None => return Err(Error::Nip05KeyNotFound), + }; + + // Save person + GLOBALS + .people + .write() + .await + .upsert_nip05_validity( + &(*pubkey).into(), + Some(dns_id.clone()), + true, + Unixtime::now().unwrap().0 as u64, + ) + .await?; + + // Mark as followed + GLOBALS + .people + .write() + .await + .async_follow(&(*pubkey).into(), true) + .await?; + + tracing::info!("Followed {}", &dns_id); + + // Set their relays + let relays = match nip05.relays.get(pubkey) { + Some(relays) => relays, + None => return Err(Error::Nip05RelaysNotFound), + }; + for relay in relays.iter() { + // Save relay + let relay_url = Url::new(relay); + if relay_url.is_valid_relay_url() { + let db_relay = DbRelay::new(relay_url.inner().to_owned())?; + DbRelay::insert(db_relay).await?; + + // Save person_relay + DbPersonRelay::upsert_last_suggested_nip05( + (*pubkey).into(), + relay.inner().to_owned(), + Unixtime::now().unwrap().0 as u64, + ) + .await?; + } + } + + tracing::info!("Setup {} relays for {}", relays.len(), &dns_id); + + Ok(()) +} + +// returns user and domain +fn parse_dns_id(dns_id: &str) -> Result<(String, String), Error> { + let mut parts: Vec<&str> = dns_id.split('@').collect(); + + // Add the underscore as a username if they just specified a domain name. + if parts.len() == 1 { + parts = Vec::from(["_", parts.first().unwrap()]) + } + + // Require two parts + if parts.len() != 2 { + Err(Error::InvalidDnsId) + } else { + let domain = parts.pop().unwrap(); + let user = parts.pop().unwrap(); + Ok((user.to_string(), domain.to_string())) + } +} + +async fn fetch_nip05(user: &str, domain: &str) -> Result { + let nip05_future = reqwest::Client::new() + .get(format!( + "https://{}/.well-known/nostr.json?name={}", + domain, user + )) + .header("Host", domain) + .send(); + let timeout_future = tokio::time::timeout(std::time::Duration::new(15, 0), nip05_future); + let response = timeout_future.await??; + Ok(response.json::().await?) +} diff --git a/src/overlord/mod.rs b/src/overlord/mod.rs index 922827f8..ff1fd45c 100644 --- a/src/overlord/mod.rs +++ b/src/overlord/mod.rs @@ -8,8 +8,7 @@ use crate::globals::{Globals, GLOBALS}; use crate::people::People; use minion::Minion; use nostr_types::{ - Event, EventKind, Id, IdHex, Nip05, PreEvent, PrivateKey, PublicKey, PublicKeyHex, Tag, - Unixtime, Url, + Event, EventKind, Id, IdHex, PreEvent, PrivateKey, PublicKey, PublicKeyHex, Tag, Unixtime, Url, }; use relay_picker::{BestRelay, RelayPicker}; use std::collections::HashMap; @@ -373,7 +372,7 @@ impl Overlord { } ToOverlordMessage::FollowNip05(dns_id) => { let _ = tokio::spawn(async move { - if let Err(e) = Overlord::get_and_follow_nip05(dns_id).await { + if let Err(e) = crate::nip05::get_and_follow_nip05(dns_id).await { tracing::error!("{}", e); } }); @@ -554,89 +553,6 @@ impl Overlord { Ok(()) } - async fn get_and_follow_nip05(nip05: String) -> Result<(), Error> { - let mut parts: Vec<&str> = nip05.split('@').collect(); - if parts.len() == 1 { - parts = Vec::from(["_", parts.first().unwrap()]) - } - if parts.len() != 2 { - return Err(Error::InvalidDnsId); - } - - let domain = parts.pop().unwrap(); - let user = parts.pop().unwrap(); - let nip05_future = reqwest::Client::new() - .get(format!( - "https://{}/.well-known/nostr.json?name={}", - domain, user - )) - .header("Host", domain) - .send(); - let timeout_future = tokio::time::timeout(std::time::Duration::new(15, 0), nip05_future); - let response = timeout_future.await??; - let nip05 = response.json::().await?; - Overlord::follow_nip05(nip05, user.to_string(), domain.to_string()).await?; - Ok(()) - } - - async fn follow_nip05(nip05: Nip05, user: String, domain: String) -> Result<(), Error> { - let dns_id = format!("{}@{}", user, domain); - - let pubkey = match nip05.names.get(&user) { - Some(pk) => pk, - None => return Err(Error::Nip05KeyNotFound), - }; - - // Save person - GLOBALS - .people - .write() - .await - .upsert_nip05_validity( - &(*pubkey).into(), - Some(dns_id.clone()), - true, - Unixtime::now().unwrap().0 as u64, - ) - .await?; - - // Mark as followed - GLOBALS - .people - .write() - .await - .async_follow(&(*pubkey).into(), true) - .await?; - - tracing::info!("Followed {}", &dns_id); - - let relays = match nip05.relays.get(pubkey) { - Some(relays) => relays, - None => return Err(Error::Nip05RelaysNotFound), - }; - - for relay in relays.iter() { - // Save relay - let relay_url = Url::new(relay); - if relay_url.is_valid_relay_url() { - let db_relay = DbRelay::new(relay_url.inner().to_owned())?; - DbRelay::insert(db_relay).await?; - - // Save person_relay - DbPersonRelay::upsert_last_suggested_nip05( - (*pubkey).into(), - relay.inner().to_owned(), - Unixtime::now().unwrap().0 as u64, - ) - .await?; - } - } - - tracing::info!("Setup {} relays for {}", relays.len(), &dns_id); - - Ok(()) - } - async fn follow_bech32(bech32: String, relay: String) -> Result<(), Error> { let pk = PublicKey::try_from_bech32_string(&bech32)?; let pkhex: PublicKeyHex = pk.into();