nostr-rs-relay

My dev fork of nostr-rs-relay
git clone git://jb55.com/nostr-rs-relay
Log | Files | Refs | README | LICENSE

nip05.rs (32692B)


      1 //! User verification using NIP-05 names
      2 //!
      3 //! NIP-05 defines a mechanism for authors to associate an internet
      4 //! address with their public key, in metadata events.  This module
      5 //! consumes a stream of metadata events, and keeps a database table
      6 //! updated with the current NIP-05 verification status.
      7 use crate::config::VerifiedUsers;
      8 use crate::db;
      9 use crate::error::{Error, Result};
     10 use crate::event::Event;
     11 use crate::utils::unix_time;
     12 use hyper::body::HttpBody;
     13 use hyper::client::connect::HttpConnector;
     14 use hyper::Client;
     15 use hyper_tls::HttpsConnector;
     16 use rand::Rng;
     17 use rusqlite::params;
     18 use std::time::Duration;
     19 use std::time::Instant;
     20 use std::time::SystemTime;
     21 use tokio::time::Interval;
     22 use tracing::{debug, info, warn};
     23 
     24 /// NIP-05 verifier state
     25 pub struct Verifier {
     26     /// Metadata events for us to inspect
     27     metadata_rx: tokio::sync::broadcast::Receiver<Event>,
     28     /// Newly validated events get written and then broadcast on this channel to subscribers
     29     event_tx: tokio::sync::broadcast::Sender<Event>,
     30     /// SQLite read query pool
     31     read_pool: db::SqlitePool,
     32     /// SQLite write query pool
     33     write_pool: db::SqlitePool,
     34     /// Settings
     35     settings: crate::config::Settings,
     36     /// HTTP client
     37     client: hyper::Client<HttpsConnector<HttpConnector>, hyper::Body>,
     38     /// After all accounts are updated, wait this long before checking again.
     39     wait_after_finish: Duration,
     40     /// Minimum amount of time between HTTP queries
     41     http_wait_duration: Duration,
     42     /// Interval for updating verification records
     43     reverify_interval: Interval,
     44 }
     45 
     46 /// A NIP-05 identifier is a local part and domain.
     47 #[derive(PartialEq, Eq, Debug, Clone)]
     48 pub struct Nip05Name {
     49     local: String,
     50     domain: String,
     51 }
     52 
     53 impl Nip05Name {
     54     /// Does this name represent the entire domain?
     55     pub fn is_domain_only(&self) -> bool {
     56         self.local == "_"
     57     }
     58 
     59     /// Determine the URL to query for verification
     60     fn to_url(&self) -> Option<http::Uri> {
     61         format!(
     62             "https://{}/.well-known/nostr.json?name={}",
     63             self.domain, self.local
     64         )
     65         .parse::<http::Uri>()
     66         .ok()
     67     }
     68 }
     69 
     70 // Parsing Nip05Names from strings
     71 impl std::convert::TryFrom<&str> for Nip05Name {
     72     type Error = Error;
     73     fn try_from(inet: &str) -> Result<Self, Self::Error> {
     74         // break full name at the @ boundary.
     75         let components: Vec<&str> = inet.split('@').collect();
     76         if components.len() != 2 {
     77             Err(Error::CustomError("too many/few components".to_owned()))
     78         } else {
     79             // check if local name is valid
     80             let local = components[0];
     81             let domain = components[1];
     82             if local
     83                 .chars()
     84                 .all(|x| x.is_alphanumeric() || x == '_' || x == '-' || x == '.')
     85             {
     86                 if domain
     87                     .chars()
     88                     .all(|x| x.is_alphanumeric() || x == '-' || x == '.')
     89                 {
     90                     Ok(Nip05Name {
     91                         local: local.to_owned(),
     92                         domain: domain.to_owned(),
     93                     })
     94                 } else {
     95                     Err(Error::CustomError(
     96                         "invalid character in domain part".to_owned(),
     97                     ))
     98                 }
     99             } else {
    100                 Err(Error::CustomError(
    101                     "invalid character in local part".to_owned(),
    102                 ))
    103             }
    104         }
    105     }
    106 }
    107 
    108 impl std::fmt::Display for Nip05Name {
    109     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    110         write!(f, "{}@{}", self.local, self.domain)
    111     }
    112 }
    113 
    114 // Current time, with a slight foward jitter in seconds
    115 fn now_jitter(sec: u64) -> u64 {
    116     // random time between now, and 10min in future.
    117     let mut rng = rand::thread_rng();
    118     let jitter_amount = rng.gen_range(0..sec);
    119     let now = unix_time();
    120     now.saturating_add(jitter_amount)
    121 }
    122 
    123 /// Check if the specified username and address are present and match in this response body
    124 fn body_contains_user(username: &str, address: &str, bytes: hyper::body::Bytes) -> Result<bool> {
    125     // convert the body into json
    126     let body: serde_json::Value = serde_json::from_slice(&bytes)?;
    127     // ensure we have a names object.
    128     let names_map = body
    129         .as_object()
    130         .and_then(|x| x.get("names"))
    131         .and_then(|x| x.as_object())
    132         .ok_or_else(|| Error::CustomError("not a map".to_owned()))?;
    133     // get the pubkey for the requested user
    134     let check_name = names_map.get(username).and_then(|x| x.as_str());
    135     // ensure the address is a match
    136     Ok(check_name.map(|x| x == address).unwrap_or(false))
    137 }
    138 
    139 impl Verifier {
    140     pub fn new(
    141         metadata_rx: tokio::sync::broadcast::Receiver<Event>,
    142         event_tx: tokio::sync::broadcast::Sender<Event>,
    143         settings: crate::config::Settings,
    144     ) -> Result<Self> {
    145         info!("creating NIP-05 verifier");
    146         // build a database connection for reading and writing.
    147         let write_pool = db::build_pool(
    148             "nip05 writer",
    149             &settings,
    150             rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE,
    151             1,    // min conns
    152             4,    // max conns
    153             true, // wait for DB
    154         );
    155         let read_pool = db::build_pool(
    156             "nip05 reader",
    157             &settings,
    158             rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
    159             1,    // min conns
    160             8,    // max conns
    161             true, // wait for DB
    162         );
    163         // setup hyper client
    164         let https = HttpsConnector::new();
    165         let client = Client::builder().build::<_, hyper::Body>(https);
    166 
    167         // After all accounts have been re-verified, don't check again
    168         // for this long.
    169         let wait_after_finish = Duration::from_secs(60 * 10);
    170         // when we have an active queue of accounts to validate, we
    171         // will wait this duration between HTTP requests.
    172         let http_wait_duration = Duration::from_secs(1);
    173         // setup initial interval for re-verification.  If we find
    174         // there is no work to be done, it will be reset to a longer
    175         // duration.
    176         let reverify_interval = tokio::time::interval(http_wait_duration);
    177         Ok(Verifier {
    178             metadata_rx,
    179             event_tx,
    180             read_pool,
    181             write_pool,
    182             settings,
    183             client,
    184             wait_after_finish,
    185             http_wait_duration,
    186             reverify_interval,
    187         })
    188     }
    189 
    190     /// Perform web verification against a NIP-05 name and address.
    191     pub async fn get_web_verification(
    192         &mut self,
    193         nip: &Nip05Name,
    194         pubkey: &str,
    195     ) -> UserWebVerificationStatus {
    196         self.get_web_verification_res(nip, pubkey)
    197             .await
    198             .unwrap_or(UserWebVerificationStatus::Unknown)
    199     }
    200 
    201     /// Perform web verification against an `Event` (must be metadata).
    202     pub async fn get_web_verification_from_event(
    203         &mut self,
    204         e: &Event,
    205     ) -> UserWebVerificationStatus {
    206         let nip_parse = e.get_nip05_addr();
    207         if let Some(nip) = nip_parse {
    208             self.get_web_verification_res(&nip, &e.pubkey)
    209                 .await
    210                 .unwrap_or(UserWebVerificationStatus::Unknown)
    211         } else {
    212             UserWebVerificationStatus::Unknown
    213         }
    214     }
    215 
    216     /// Perform web verification, with a `Result` return.
    217     async fn get_web_verification_res(
    218         &mut self,
    219         nip: &Nip05Name,
    220         pubkey: &str,
    221     ) -> Result<UserWebVerificationStatus> {
    222         // determine if this domain should be checked
    223         if !is_domain_allowed(
    224             &nip.domain,
    225             &self.settings.verified_users.domain_whitelist,
    226             &self.settings.verified_users.domain_blacklist,
    227         ) {
    228             return Ok(UserWebVerificationStatus::DomainNotAllowed);
    229         }
    230         let url = nip
    231             .to_url()
    232             .ok_or_else(|| Error::CustomError("invalid NIP-05 URL".to_owned()))?;
    233         let req = hyper::Request::builder()
    234             .method(hyper::Method::GET)
    235             .uri(url)
    236             .header("Accept", "application/json")
    237             .header(
    238                 "User-Agent",
    239                 format!(
    240                     "nostr-rs-relay/{} NIP-05 Verifier",
    241                     crate::info::CARGO_PKG_VERSION.unwrap()
    242                 ),
    243             )
    244             .body(hyper::Body::empty())
    245             .expect("request builder");
    246 
    247         let response_fut = self.client.request(req);
    248 
    249         // HTTP request with timeout
    250         match tokio::time::timeout(Duration::from_secs(5), response_fut).await {
    251             Ok(response_res) => {
    252                 // limit size of verification document to 1MB.
    253                 const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024 * 1024;
    254                 let response = response_res?;
    255                 // determine content length from response
    256                 let response_content_length = match response.body().size_hint().upper() {
    257                     Some(v) => v,
    258                     None => MAX_ALLOWED_RESPONSE_SIZE + 1, // reject missing content length
    259                 };
    260                 // TODO: test how hyper handles the client providing an inaccurate content-length.
    261                 if response_content_length <= MAX_ALLOWED_RESPONSE_SIZE {
    262                     let (parts, body) = response.into_parts();
    263                     // TODO: consider redirects
    264                     if parts.status == http::StatusCode::OK {
    265                         // parse body, determine if the username / key / address is present
    266                         let body_bytes = hyper::body::to_bytes(body).await?;
    267                         let body_matches = body_contains_user(&nip.local, pubkey, body_bytes)?;
    268                         if body_matches {
    269                             return Ok(UserWebVerificationStatus::Verified);
    270                         }
    271                         // successful response, parsed as a nip-05
    272                         // document, but this name/pubkey was not
    273                         // present.
    274                         return Ok(UserWebVerificationStatus::Unverified);
    275                     }
    276                 } else {
    277                     info!(
    278                         "content length missing or exceeded limits for account: {:?}",
    279                         nip.to_string()
    280                     );
    281                 }
    282             }
    283             Err(_) => {
    284                 info!("timeout verifying account {:?}", nip);
    285                 return Ok(UserWebVerificationStatus::Unknown);
    286             }
    287         }
    288         Ok(UserWebVerificationStatus::Unknown)
    289     }
    290 
    291     /// Perform NIP-05 verifier tasks.
    292     pub async fn run(&mut self) {
    293         // use this to schedule periodic re-validation tasks
    294         // run a loop, restarting on failure
    295         loop {
    296             let res = self.run_internal().await;
    297             if let Err(e) = res {
    298                 info!("error in verifier: {:?}", e);
    299             }
    300         }
    301     }
    302 
    303     /// Internal select loop for performing verification
    304     async fn run_internal(&mut self) -> Result<()> {
    305         tokio::select! {
    306             m = self.metadata_rx.recv() => {
    307                 match m {
    308                     Ok(e) => {
    309                         if let Some(naddr) = e.get_nip05_addr() {
    310                             info!("got metadata event for ({:?},{:?})", naddr.to_string() ,e.get_author_prefix());
    311                             // Process a new author, checking if they are verified:
    312                             let check_verified = get_latest_user_verification(self.read_pool.get().expect("could not get connection"), &e.pubkey).await;
    313                             // ensure the event we got is more recent than the one we have, otherwise we can ignore it.
    314                             if let Ok(last_check) = check_verified {
    315                                 if e.created_at <= last_check.event_created {
    316                                     // this metadata is from the same author as an existing verification.
    317                                     // it is older than what we have, so we can ignore it.
    318                                     debug!("received older metadata event for author {:?}", e.get_author_prefix());
    319                                     return Ok(());
    320                                 }
    321                             }
    322                             // old, or no existing record for this user.  In either case, we just create a new one.
    323                             let start = Instant::now();
    324                             let v = self.get_web_verification_from_event(&e).await;
    325                             info!(
    326                                 "checked name {:?}, result: {:?}, in: {:?}",
    327                                 naddr.to_string(),
    328                                 v,
    329                                 start.elapsed()
    330                             );
    331                             // sleep to limit how frequently we make HTTP requests for new metadata events.  This should limit us to 4 req/sec.
    332                             tokio::time::sleep(Duration::from_millis(250)).await;
    333                             // if this user was verified, we need to write the
    334                             // record, persist the event, and broadcast.
    335                             if let UserWebVerificationStatus::Verified = v {
    336                                 self.create_new_verified_user(&naddr.to_string(), &e).await?;
    337                             }
    338                         }
    339                     },
    340                     Err(tokio::sync::broadcast::error::RecvError::Lagged(c)) => {
    341                         warn!("incoming metadata events overwhelmed buffer, {} events dropped",c);
    342                     }
    343                     Err(tokio::sync::broadcast::error::RecvError::Closed) => {
    344                         info!("metadata broadcast channel closed");
    345                     }
    346                 }
    347             },
    348             _ = self.reverify_interval.tick() => {
    349                 // check and see if there is an old account that needs
    350                 // to be reverified
    351                 self.do_reverify().await?;
    352             },
    353         }
    354         Ok(())
    355     }
    356 
    357     /// Reverify the oldest user verification record.
    358     async fn do_reverify(&mut self) -> Result<()> {
    359         let reverify_setting = self
    360             .settings
    361             .verified_users
    362             .verify_update_frequency_duration;
    363         let max_failures = self.settings.verified_users.max_consecutive_failures;
    364         // get from settings, but default to 6hrs between re-checking an account
    365         let reverify_dur = reverify_setting.unwrap_or_else(|| Duration::from_secs(60 * 60 * 6));
    366         // find all verification records that have success or failure OLDER than the reverify_dur.
    367         let now = SystemTime::now();
    368         let earliest = now - reverify_dur;
    369         let earliest_epoch = earliest
    370             .duration_since(SystemTime::UNIX_EPOCH)
    371             .map(|x| x.as_secs())
    372             .unwrap_or(0);
    373         let vr = get_oldest_user_verification(self.read_pool.get()?, earliest_epoch).await;
    374         match vr {
    375             Ok(ref v) => {
    376                 let new_status = self.get_web_verification(&v.name, &v.address).await;
    377                 match new_status {
    378                     UserWebVerificationStatus::Verified => {
    379                         // freshly verified account, update the
    380                         // timestamp.
    381                         self.update_verification_record(self.write_pool.get()?, v)
    382                             .await?;
    383                     }
    384                     UserWebVerificationStatus::DomainNotAllowed
    385                     | UserWebVerificationStatus::Unknown => {
    386                         // server may be offline, or temporarily
    387                         // blocked by the config file.  Note the
    388                         // failure so we can process something
    389                         // else.
    390 
    391                         // have we had enough failures to give up?
    392                         if v.failure_count >= max_failures as u64 {
    393                             info!(
    394                                 "giving up on verifying {:?} after {} failures",
    395                                 v.name, v.failure_count
    396                             );
    397                             self.delete_verification_record(self.write_pool.get()?, v)
    398                                 .await?;
    399                         } else {
    400                             // record normal failure, incrementing failure count
    401                             self.fail_verification_record(self.write_pool.get()?, v)
    402                                 .await?;
    403                         }
    404                     }
    405                     UserWebVerificationStatus::Unverified => {
    406                         // domain has removed the verification, drop
    407                         // the record on our side.
    408                         self.delete_verification_record(self.write_pool.get()?, v)
    409                             .await?;
    410                     }
    411                 }
    412             }
    413             Err(Error::SqlError(rusqlite::Error::QueryReturnedNoRows)) => {
    414                 // No users need verification.  Reset the interval to
    415                 // the next verification attempt.
    416                 let start = tokio::time::Instant::now() + self.wait_after_finish;
    417                 self.reverify_interval = tokio::time::interval_at(start, self.http_wait_duration);
    418             }
    419             Err(ref e) => {
    420                 warn!(
    421                     "Error when checking for NIP-05 verification records: {:?}",
    422                     e
    423                 );
    424             }
    425         }
    426         Ok(())
    427     }
    428 
    429     /// Reset the verification timestamp on a VerificationRecord
    430     pub async fn update_verification_record(
    431         &mut self,
    432         mut conn: db::PooledConnection,
    433         vr: &VerificationRecord,
    434     ) -> Result<()> {
    435         let vr_id = vr.rowid;
    436         let vr_str = vr.to_string();
    437         tokio::task::spawn_blocking(move || {
    438             // add some jitter to the verification to prevent everything from stacking up together.
    439             let verif_time = now_jitter(600);
    440             let tx = conn.transaction()?;
    441             {
    442                 // update verification time and reset any failure count
    443                 let query =
    444                     "UPDATE user_verification SET verified_at=?, failure_count=0 WHERE id=?";
    445                 let mut stmt = tx.prepare(query)?;
    446                 stmt.execute(params![verif_time, vr_id])?;
    447             }
    448             tx.commit()?;
    449             info!("verification updated for {}", vr_str);
    450             let ok: Result<()> = Ok(());
    451             ok
    452         })
    453         .await?
    454     }
    455     /// Reset the failure timestamp on a VerificationRecord
    456     pub async fn fail_verification_record(
    457         &mut self,
    458         mut conn: db::PooledConnection,
    459         vr: &VerificationRecord,
    460     ) -> Result<()> {
    461         let vr_id = vr.rowid;
    462         let vr_str = vr.to_string();
    463         let fail_count = vr.failure_count.saturating_add(1);
    464         tokio::task::spawn_blocking(move || {
    465             // add some jitter to the verification to prevent everything from stacking up together.
    466             let fail_time = now_jitter(600);
    467             let tx = conn.transaction()?;
    468             {
    469                 let query = "UPDATE user_verification SET failed_at=?, failure_count=? WHERE id=?";
    470                 let mut stmt = tx.prepare(query)?;
    471                 stmt.execute(params![fail_time, fail_count, vr_id])?;
    472             }
    473             tx.commit()?;
    474             info!("verification failed for {}", vr_str);
    475             let ok: Result<()> = Ok(());
    476             ok
    477         })
    478         .await?
    479     }
    480     /// Delete a VerificationRecord that is no longer valid
    481     pub async fn delete_verification_record(
    482         &mut self,
    483         mut conn: db::PooledConnection,
    484         vr: &VerificationRecord,
    485     ) -> Result<()> {
    486         let vr_id = vr.rowid;
    487         let vr_str = vr.to_string();
    488         tokio::task::spawn_blocking(move || {
    489             let tx = conn.transaction()?;
    490             {
    491                 let query = "DELETE FROM user_verification WHERE id=?;";
    492                 let mut stmt = tx.prepare(query)?;
    493                 stmt.execute(params![vr_id])?;
    494             }
    495             tx.commit()?;
    496             info!("verification rescinded for {}", vr_str);
    497             let ok: Result<()> = Ok(());
    498             ok
    499         })
    500         .await?
    501     }
    502 
    503     /// Persist an event, create a verification record, and broadcast.
    504     // TODO: have more event-writing logic handled in the db module.
    505     // Right now, these events avoid the rate limit.  That is
    506     // acceptable since as soon as the user is registered, this path
    507     // is no longer used.
    508     // TODO: refactor these into spawn_blocking
    509     // calls to get them off the async executors.
    510     async fn create_new_verified_user(&mut self, name: &str, event: &Event) -> Result<()> {
    511         let start = Instant::now();
    512         // we should only do this if we are enabled.  if we are
    513         // disabled/passive, the event has already been persisted.
    514         let should_write_event = self.settings.verified_users.is_enabled();
    515         if should_write_event {
    516             match db::write_event(&mut self.write_pool.get()?, event) {
    517                 Ok(updated) => {
    518                     if updated != 0 {
    519                         info!(
    520                             "persisted event: {:?} in {:?}",
    521                             event.get_event_id_prefix(),
    522                             start.elapsed()
    523                         );
    524                         self.event_tx.send(event.clone()).ok();
    525                     }
    526                 }
    527                 Err(err) => {
    528                     warn!("event insert failed: {:?}", err);
    529                     if let Error::SqlError(r) = err {
    530                         warn!("because: : {:?}", r);
    531                     }
    532                 }
    533             }
    534         }
    535         // write the verification record
    536         save_verification_record(self.write_pool.get()?, event, name).await?;
    537         Ok(())
    538     }
    539 }
    540 
    541 /// Result of checking user's verification status against DNS/HTTP.
    542 #[derive(PartialEq, Eq, Debug, Clone)]
    543 pub enum UserWebVerificationStatus {
    544     Verified,         // user is verified, as of now.
    545     DomainNotAllowed, // domain blacklist or whitelist denied us from attempting a verification
    546     Unknown,          // user's status could not be determined (timeout, server error)
    547     Unverified,       // user's status is not verified (successful check, name / addr do not match)
    548 }
    549 
    550 /// A NIP-05 verification record.
    551 #[derive(PartialEq, Eq, Debug, Clone)]
    552 // Basic information for a verification event.  Gives us all we need to assert a NIP-05 address is good.
    553 pub struct VerificationRecord {
    554     pub rowid: u64,                // database row for this verification event
    555     pub name: Nip05Name,           // address being verified
    556     pub address: String,           // pubkey
    557     pub event: String,             // event ID hash providing the verification
    558     pub event_created: u64,        // when the metadata event was published
    559     pub last_success: Option<u64>, // the most recent time a verification was provided.  None if verification under this name has never succeeded.
    560     pub last_failure: Option<u64>, // the most recent time verification was attempted, but could not be completed.
    561     pub failure_count: u64,        // how many consecutive failures have been observed.
    562 }
    563 
    564 /// Check with settings to determine if a given domain is allowed to
    565 /// publish.
    566 pub fn is_domain_allowed(
    567     domain: &str,
    568     whitelist: &Option<Vec<String>>,
    569     blacklist: &Option<Vec<String>>,
    570 ) -> bool {
    571     // if there is a whitelist, domain must be present in it.
    572     if let Some(wl) = whitelist {
    573         // workaround for Vec contains not accepting &str
    574         return wl.iter().any(|x| x == domain);
    575     }
    576     // otherwise, check that user is not in the blacklist
    577     if let Some(bl) = blacklist {
    578         return !bl.iter().any(|x| x == domain);
    579     }
    580     true
    581 }
    582 
    583 impl VerificationRecord {
    584     /// Check if the record is recent enough to be considered valid,
    585     /// and the domain is allowed.
    586     pub fn is_valid(&self, verified_users_settings: &VerifiedUsers) -> bool {
    587         //let settings = SETTINGS.read().unwrap();
    588         // how long a verification record is good for
    589         let nip05_expiration = &verified_users_settings.verify_expiration_duration;
    590         if let Some(e) = nip05_expiration {
    591             if !self.is_current(e) {
    592                 return false;
    593             }
    594         }
    595         // check domains
    596         is_domain_allowed(
    597             &self.name.domain,
    598             &verified_users_settings.domain_whitelist,
    599             &verified_users_settings.domain_blacklist,
    600         )
    601     }
    602 
    603     /// Check if this record has been validated since the given
    604     /// duration.
    605     fn is_current(&self, d: &Duration) -> bool {
    606         match self.last_success {
    607             Some(s) => {
    608                 // current time - duration
    609                 let now = SystemTime::now();
    610                 let cutoff = now - *d;
    611                 let cutoff_epoch = cutoff
    612                     .duration_since(SystemTime::UNIX_EPOCH)
    613                     .map(|x| x.as_secs())
    614                     .unwrap_or(0);
    615                 s > cutoff_epoch
    616             }
    617             None => false,
    618         }
    619     }
    620 }
    621 
    622 impl std::fmt::Display for VerificationRecord {
    623     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    624         write!(
    625             f,
    626             "({:?},{:?})",
    627             self.name.to_string(),
    628             self.address.chars().take(8).collect::<String>()
    629         )
    630     }
    631 }
    632 
    633 /// Create a new verification record based on an event
    634 pub async fn save_verification_record(
    635     mut conn: db::PooledConnection,
    636     event: &Event,
    637     name: &str,
    638 ) -> Result<()> {
    639     let e = hex::decode(&event.id).ok();
    640     let n = name.to_owned();
    641     let a_prefix = event.get_author_prefix();
    642     tokio::task::spawn_blocking(move || {
    643         let tx = conn.transaction()?;
    644         {
    645             // if we create a /new/ one, we should get rid of any old ones.  or group the new ones by name and only consider the latest.
    646             let query = "INSERT INTO user_verification (metadata_event, name, verified_at) VALUES ((SELECT id from event WHERE event_hash=?), ?, strftime('%s','now'));";
    647             let mut stmt = tx.prepare(query)?;
    648             stmt.execute(params![e, n])?;
    649             // get the row ID
    650             let v_id = tx.last_insert_rowid();
    651             // delete everything else by this name
    652             let del_query = "DELETE FROM user_verification WHERE name = ? AND id != ?;";
    653             let mut del_stmt = tx.prepare(del_query)?;
    654             let count = del_stmt.execute(params![n,v_id])?;
    655             if count > 0 {
    656                 info!("removed {} old verification records for ({:?},{:?})", count, n, a_prefix);
    657             }
    658         }
    659         tx.commit()?;
    660         info!("saved new verification record for ({:?},{:?})", n, a_prefix);
    661         let ok: Result<()> = Ok(());
    662         ok
    663     }).await?
    664 }
    665 
    666 /// Retrieve the most recent verification record for a given pubkey (async).
    667 pub async fn get_latest_user_verification(
    668     conn: db::PooledConnection,
    669     pubkey: &str,
    670 ) -> Result<VerificationRecord> {
    671     let p = pubkey.to_owned();
    672     tokio::task::spawn_blocking(move || query_latest_user_verification(conn, p)).await?
    673 }
    674 
    675 /// Query database for the latest verification record for a given pubkey.
    676 pub fn query_latest_user_verification(
    677     mut conn: db::PooledConnection,
    678     pubkey: String,
    679 ) -> Result<VerificationRecord> {
    680     let tx = conn.transaction()?;
    681     let query = "SELECT v.id, v.name, e.event_hash, e.created_at, v.verified_at, v.failed_at, v.failure_count FROM user_verification v LEFT JOIN event e ON e.id=v.metadata_event WHERE e.author=? ORDER BY e.created_at DESC, v.verified_at DESC, v.failed_at DESC LIMIT 1;";
    682     let mut stmt = tx.prepare_cached(query)?;
    683     let fields = stmt.query_row(params![hex::decode(&pubkey).ok()], |r| {
    684         let rowid: u64 = r.get(0)?;
    685         let rowname: String = r.get(1)?;
    686         let eventid: Vec<u8> = r.get(2)?;
    687         let created_at: u64 = r.get(3)?;
    688         // create a tuple since we can't throw non-rusqlite errors in this closure
    689         Ok((
    690             rowid,
    691             rowname,
    692             eventid,
    693             created_at,
    694             r.get(4).ok(),
    695             r.get(5).ok(),
    696             r.get(6)?,
    697         ))
    698     })?;
    699     Ok(VerificationRecord {
    700         rowid: fields.0,
    701         name: Nip05Name::try_from(&fields.1[..])?,
    702         address: pubkey,
    703         event: hex::encode(fields.2),
    704         event_created: fields.3,
    705         last_success: fields.4,
    706         last_failure: fields.5,
    707         failure_count: fields.6,
    708     })
    709 }
    710 
    711 /// Retrieve the oldest user verification (async)
    712 pub async fn get_oldest_user_verification(
    713     conn: db::PooledConnection,
    714     earliest: u64,
    715 ) -> Result<VerificationRecord> {
    716     tokio::task::spawn_blocking(move || query_oldest_user_verification(conn, earliest)).await?
    717 }
    718 
    719 pub fn query_oldest_user_verification(
    720     mut conn: db::PooledConnection,
    721     earliest: u64,
    722 ) -> Result<VerificationRecord> {
    723     let tx = conn.transaction()?;
    724     let query = "SELECT v.id, v.name, e.event_hash, e.author, e.created_at, v.verified_at, v.failed_at, v.failure_count FROM user_verification v LEFT JOIN event e ON e.id=v.metadata_event WHERE (v.verified_at < ? OR v.verified_at IS NULL) AND (v.failed_at < ? OR v.failed_at IS NULL) ORDER BY v.verified_at ASC, v.failed_at ASC LIMIT 1;";
    725     let mut stmt = tx.prepare_cached(query)?;
    726     let fields = stmt.query_row(params![earliest, earliest], |r| {
    727         let rowid: u64 = r.get(0)?;
    728         let rowname: String = r.get(1)?;
    729         let eventid: Vec<u8> = r.get(2)?;
    730         let pubkey: Vec<u8> = r.get(3)?;
    731         let created_at: u64 = r.get(4)?;
    732         // create a tuple since we can't throw non-rusqlite errors in this closure
    733         Ok((
    734             rowid,
    735             rowname,
    736             eventid,
    737             pubkey,
    738             created_at,
    739             r.get(5).ok(),
    740             r.get(6).ok(),
    741             r.get(7)?,
    742         ))
    743     })?;
    744     let vr = VerificationRecord {
    745         rowid: fields.0,
    746         name: Nip05Name::try_from(&fields.1[..])?,
    747         address: hex::encode(fields.3),
    748         event: hex::encode(fields.2),
    749         event_created: fields.4,
    750         last_success: fields.5,
    751         last_failure: fields.6,
    752         failure_count: fields.7,
    753     };
    754     Ok(vr)
    755 }
    756 
    757 #[cfg(test)]
    758 mod tests {
    759     use super::*;
    760 
    761     #[test]
    762     fn local_from_inet() {
    763         let addr = "bob@example.com";
    764         let parsed = Nip05Name::try_from(addr);
    765         assert!(!parsed.is_err());
    766         let v = parsed.unwrap();
    767         assert_eq!(v.local, "bob");
    768         assert_eq!(v.domain, "example.com");
    769     }
    770 
    771     #[test]
    772     fn not_enough_sep() {
    773         let addr = "bob_example.com";
    774         let parsed = Nip05Name::try_from(addr);
    775         assert!(parsed.is_err());
    776     }
    777 
    778     #[test]
    779     fn too_many_sep() {
    780         let addr = "foo@bob@example.com";
    781         let parsed = Nip05Name::try_from(addr);
    782         assert!(parsed.is_err());
    783     }
    784 
    785     #[test]
    786     fn invalid_local_name() {
    787         // non-permitted ascii chars
    788         assert!(Nip05Name::try_from("foo!@example.com").is_err());
    789         assert!(Nip05Name::try_from("foo @example.com").is_err());
    790         assert!(Nip05Name::try_from(" foo@example.com").is_err());
    791         assert!(Nip05Name::try_from("f oo@example.com").is_err());
    792         assert!(Nip05Name::try_from("foo<@example.com").is_err());
    793         // unicode dash
    794         assert!(Nip05Name::try_from("foo‐bar@example.com").is_err());
    795         // emoji
    796         assert!(Nip05Name::try_from("foo😭bar@example.com").is_err());
    797     }
    798     #[test]
    799     fn invalid_domain_name() {
    800         // non-permitted ascii chars
    801         assert!(Nip05Name::try_from("foo@examp!e.com").is_err());
    802         assert!(Nip05Name::try_from("foo@ example.com").is_err());
    803         assert!(Nip05Name::try_from("foo@exa mple.com").is_err());
    804         assert!(Nip05Name::try_from("foo@example .com").is_err());
    805         assert!(Nip05Name::try_from("foo@exa<mple.com").is_err());
    806         // unicode dash
    807         assert!(Nip05Name::try_from("foobar@exa‐mple.com").is_err());
    808         // emoji
    809         assert!(Nip05Name::try_from("foobar@ex😭ample.com").is_err());
    810     }
    811 
    812     #[test]
    813     fn to_url() {
    814         let nip = Nip05Name::try_from("foobar@example.com").unwrap();
    815         assert_eq!(
    816             nip.to_url(),
    817             Some(
    818                 "https://example.com/.well-known/nostr.json?name=foobar"
    819                     .parse()
    820                     .unwrap()
    821             )
    822         );
    823     }
    824 }