gossip/src/nip05.rs

191 lines
5.4 KiB
Rust
Raw Normal View History

use crate::db::PersonRelay;
use crate::error::{Error, ErrorKind};
2023-01-06 14:09:58 +00:00
use crate::globals::GLOBALS;
use crate::people::Person;
2023-01-30 00:16:25 +00:00
use nostr_types::{Metadata, Nip05, PublicKeyHex, RelayUrl, Unixtime};
use std::sync::atomic::Ordering;
2023-01-06 14:09:58 +00:00
// This updates the people map and the database with the result
pub async fn validate_nip05(person: Person) -> Result<(), Error> {
if !GLOBALS.settings.read().check_nip05 {
2023-01-23 01:05:20 +00:00
return Ok(());
}
2023-01-06 14:09:58 +00:00
let now = Unixtime::now().unwrap();
// invalid if their nip-05 is not set
if person.metadata.is_none() || matches!(person.metadata, Some(Metadata { nip05: None, .. })) {
2023-01-06 14:09:58 +00:00
GLOBALS
.people
.upsert_nip05_validity(&person.pubkey, None, false, now.0 as u64)
2023-01-06 14:09:58 +00:00
.await?;
return Ok(());
}
let metadata = person.metadata.as_ref().unwrap().to_owned();
let nip05 = metadata.nip05.as_ref().unwrap().to_owned();
2023-01-06 14:09:58 +00:00
// Split their DNS ID
let (user, domain) = match parse_nip05(&nip05) {
2023-01-06 14:09:58 +00:00
Ok(pair) => pair,
Err(_) => {
GLOBALS
.people
.upsert_nip05_validity(&person.pubkey, Some(nip05), false, now.0 as u64)
2023-01-06 14:09:58 +00:00
.await?;
return Ok(());
}
};
// Fetch NIP-05
let nip05file = match fetch_nip05(&user, &domain).await {
Ok(content) => content,
Err(e) => {
tracing::error!("NIP-05 fetch issue with {}@{}", user, domain);
return Err(e);
}
};
2023-01-06 14:09:58 +00:00
// Check if the response matches their public key
let mut valid = false;
match nip05file.names.get(&user) {
2023-01-06 14:09:58 +00:00
Some(pk) => {
2023-01-11 07:42:48 +00:00
if *pk == person.pubkey {
2023-01-06 14:09:58 +00:00
// Validated
GLOBALS
.people
.upsert_nip05_validity(&person.pubkey, Some(nip05.clone()), true, now.0 as u64)
2023-01-06 14:09:58 +00:00
.await?;
valid = true;
2023-01-06 14:09:58 +00:00
}
}
None => {
// Failed
GLOBALS
.people
.upsert_nip05_validity(&person.pubkey, Some(nip05.clone()), false, now.0 as u64)
2023-01-06 14:09:58 +00:00
.await?;
}
}
// UI cache invalidation (so notes of the person get rerendered)
GLOBALS
.ui_people_to_invalidate
.write()
.push(person.pubkey.clone());
if valid {
update_relays(nip05, nip05file, &person.pubkey).await?;
}
2023-01-06 14:09:58 +00:00
Ok(())
}
pub async fn get_and_follow_nip05(nip05: String) -> Result<(), Error> {
2023-01-06 14:09:58 +00:00
// Split their DNS ID
let (user, domain) = parse_nip05(&nip05)?;
2023-01-06 14:09:58 +00:00
// Fetch NIP-05
let nip05file = fetch_nip05(&user, &domain).await?;
2023-01-06 14:09:58 +00:00
// Get their pubkey
let pubkey = match nip05file.names.get(&user) {
Some(pk) => pk.to_owned(),
None => return Err((ErrorKind::Nip05KeyNotFound, file!(), line!()).into()),
2023-01-06 14:09:58 +00:00
};
// Save person
GLOBALS
.people
.upsert_nip05_validity(
&pubkey,
Some(nip05.clone()),
2023-01-06 14:09:58 +00:00
true,
Unixtime::now().unwrap().0 as u64,
)
.await?;
// Mark as followed
GLOBALS.people.async_follow(&pubkey, true).await?;
2023-01-06 14:09:58 +00:00
tracing::info!("Followed {}", &nip05);
2023-01-06 14:09:58 +00:00
update_relays(nip05, nip05file, &pubkey).await?;
Ok(())
}
async fn update_relays(
nip05: String,
nip05file: Nip05,
pubkey: &PublicKeyHex,
) -> Result<(), Error> {
2023-01-06 14:09:58 +00:00
// Set their relays
let relays = match nip05file.relays.get(pubkey) {
2023-01-06 14:09:58 +00:00
Some(relays) => relays,
None => return Ok(()),
2023-01-06 14:09:58 +00:00
};
for relay in relays.iter() {
// Save relay
2023-01-30 00:16:25 +00:00
if let Ok(relay_url) = RelayUrl::try_from_unchecked_url(relay) {
GLOBALS.storage.write_relay_if_missing(&relay_url)?;
2023-01-06 14:09:58 +00:00
// Save person_relay
PersonRelay::upsert_last_suggested_nip05(
pubkey.to_owned(),
2023-01-30 00:16:25 +00:00
relay_url,
2023-01-06 14:09:58 +00:00
Unixtime::now().unwrap().0 as u64,
)
.await?;
}
}
tracing::info!("Setup {} relays for {}", relays.len(), &nip05);
2023-01-06 14:09:58 +00:00
Ok(())
}
// returns user and domain
fn parse_nip05(nip05: &str) -> Result<(String, String), Error> {
let mut parts: Vec<&str> = nip05.split('@').collect();
2023-01-06 14:09:58 +00:00
// 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((ErrorKind::InvalidDnsId, file!(), line!()).into())
2023-01-06 14:09:58 +00:00
} else {
let domain = parts.pop().unwrap();
let user = parts.pop().unwrap();
if domain.len() < 4 {
// smallest non-TLD domain is like 't.co'
return Err((ErrorKind::InvalidDnsId, file!(), line!()).into());
}
2023-01-06 14:09:58 +00:00
Ok((user.to_string(), domain.to_string()))
}
}
async fn fetch_nip05(user: &str, domain: &str) -> Result<Nip05, Error> {
2023-03-04 04:23:26 +00:00
// FIXME add user-agent if configured
2023-01-07 08:05:53 +00:00
let nip05_future = reqwest::Client::builder()
.timeout(std::time::Duration::new(60, 0))
.redirect(reqwest::redirect::Policy::none()) // see NIP-05
.gzip(true)
.brotli(true)
.deflate(true)
2023-01-07 08:05:53 +00:00
.build()?
2023-01-06 14:09:58 +00:00
.get(format!(
"https://{}/.well-known/nostr.json?name={}",
domain, user
))
.send();
2023-01-07 08:05:53 +00:00
let response = nip05_future.await?;
let bytes = response.bytes().await?;
GLOBALS.bytes_read.fetch_add(bytes.len(), Ordering::Relaxed);
Ok(serde_json::from_slice(&bytes)?)
2023-01-06 14:09:58 +00:00
}