nostr_rs_relay/
nip05.rs

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.
7use crate::config::VerifiedUsers;
8use crate::error::{Error, Result};
9use crate::event::Event;
10use crate::repo::NostrRepo;
11use hyper::body::HttpBody;
12use hyper::client::connect::HttpConnector;
13use hyper::Client;
14use hyper_tls::HttpsConnector;
15use std::sync::Arc;
16use std::time::Duration;
17use std::time::Instant;
18use std::time::SystemTime;
19use tokio::time::Interval;
20use tracing::{debug, info, warn};
21
22/// NIP-05 verifier state
23pub struct Verifier {
24    /// Repository for saving/retrieving events and records
25    repo: Arc<dyn NostrRepo>,
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    /// Settings
31    settings: crate::config::Settings,
32    /// HTTP client
33    client: hyper::Client<HttpsConnector<HttpConnector>, hyper::Body>,
34    /// After all accounts are updated, wait this long before checking again.
35    wait_after_finish: Duration,
36    /// Minimum amount of time between HTTP queries
37    http_wait_duration: Duration,
38    /// Interval for updating verification records
39    reverify_interval: Interval,
40}
41
42/// A NIP-05 identifier is a local part and domain.
43#[derive(PartialEq, Eq, Debug, Clone)]
44pub struct Nip05Name {
45    pub local: String,
46    pub domain: String,
47}
48
49impl Nip05Name {
50    /// Does this name represent the entire domain?
51    #[must_use]
52    pub fn is_domain_only(&self) -> bool {
53        self.local == "_"
54    }
55
56    /// Determine the URL to query for verification
57    fn to_url(&self) -> Option<http::Uri> {
58        format!(
59            "https://{}/.well-known/nostr.json?name={}",
60            self.domain, self.local
61        )
62        .parse::<http::Uri>()
63        .ok()
64    }
65}
66
67// Parsing Nip05Names from strings
68impl std::convert::TryFrom<&str> for Nip05Name {
69    type Error = Error;
70    fn try_from(inet: &str) -> Result<Self, Self::Error> {
71        // break full name at the @ boundary.
72        let components: Vec<&str> = inet.split('@').collect();
73        if components.len() == 2 {
74            // check if local name is valid
75            let local = components[0];
76            let domain = components[1];
77            if local
78                .chars()
79                .all(|x| x.is_alphanumeric() || x == '_' || x == '-' || x == '.')
80            {
81                if domain
82                    .chars()
83                    .all(|x| x.is_alphanumeric() || x == '-' || x == '.')
84                {
85                    Ok(Nip05Name {
86                        local: local.to_owned(),
87                        domain: domain.to_owned(),
88                    })
89                } else {
90                    Err(Error::CustomError(
91                        "invalid character in domain part".to_owned(),
92                    ))
93                }
94            } else {
95                Err(Error::CustomError(
96                    "invalid character in local part".to_owned(),
97                ))
98            }
99        } else {
100            Err(Error::CustomError("too many/few components".to_owned()))
101        }
102    }
103}
104
105impl std::fmt::Display for Nip05Name {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        write!(f, "{}@{}", self.local, self.domain)
108    }
109}
110
111/// Check if the specified username and address are present and match in this response body
112fn body_contains_user(username: &str, address: &str, bytes: &hyper::body::Bytes) -> Result<bool> {
113    // convert the body into json
114    let body: serde_json::Value = serde_json::from_slice(bytes)?;
115    // ensure we have a names object.
116    let names_map = body
117        .as_object()
118        .and_then(|x| x.get("names"))
119        .and_then(serde_json::Value::as_object)
120        .ok_or_else(|| Error::CustomError("not a map".to_owned()))?;
121    // get the pubkey for the requested user
122    let check_name = names_map.get(username).and_then(serde_json::Value::as_str);
123    // ensure the address is a match
124    Ok(check_name.map_or(false, |x| x == address))
125}
126
127impl Verifier {
128    pub fn new(
129        repo: Arc<dyn NostrRepo>,
130        metadata_rx: tokio::sync::broadcast::Receiver<Event>,
131        event_tx: tokio::sync::broadcast::Sender<Event>,
132        settings: crate::config::Settings,
133    ) -> Result<Self> {
134        info!("creating NIP-05 verifier");
135        // setup hyper client
136        let https = HttpsConnector::new();
137        let client = Client::builder().build::<_, hyper::Body>(https);
138
139        // After all accounts have been re-verified, don't check again
140        // for this long.
141        let wait_after_finish = Duration::from_secs(60 * 10);
142        // when we have an active queue of accounts to validate, we
143        // will wait this duration between HTTP requests.
144        let http_wait_duration = Duration::from_secs(1);
145        // setup initial interval for re-verification.  If we find
146        // there is no work to be done, it will be reset to a longer
147        // duration.
148        let reverify_interval = tokio::time::interval(http_wait_duration);
149        Ok(Verifier {
150            repo,
151            metadata_rx,
152            event_tx,
153            settings,
154            client,
155            wait_after_finish,
156            http_wait_duration,
157            reverify_interval,
158        })
159    }
160
161    /// Perform web verification against a NIP-05 name and address.
162    pub async fn get_web_verification(
163        &mut self,
164        nip: &Nip05Name,
165        pubkey: &str,
166    ) -> UserWebVerificationStatus {
167        self.get_web_verification_res(nip, pubkey)
168            .await
169            .unwrap_or(UserWebVerificationStatus::Unknown)
170    }
171
172    /// Perform web verification against an `Event` (must be metadata).
173    pub async fn get_web_verification_from_event(
174        &mut self,
175        e: &Event,
176    ) -> UserWebVerificationStatus {
177        let nip_parse = e.get_nip05_addr();
178        if let Some(nip) = nip_parse {
179            self.get_web_verification_res(&nip, &e.pubkey)
180                .await
181                .unwrap_or(UserWebVerificationStatus::Unknown)
182        } else {
183            UserWebVerificationStatus::Unknown
184        }
185    }
186
187    /// Perform web verification, with a `Result` return.
188    async fn get_web_verification_res(
189        &mut self,
190        nip: &Nip05Name,
191        pubkey: &str,
192    ) -> Result<UserWebVerificationStatus> {
193        // determine if this domain should be checked
194        if !is_domain_allowed(
195            &nip.domain,
196            &self.settings.verified_users.domain_whitelist,
197            &self.settings.verified_users.domain_blacklist,
198        ) {
199            return Ok(UserWebVerificationStatus::DomainNotAllowed);
200        }
201        let url = nip
202            .to_url()
203            .ok_or_else(|| Error::CustomError("invalid NIP-05 URL".to_owned()))?;
204        let req = hyper::Request::builder()
205            .method(hyper::Method::GET)
206            .uri(url)
207            .header("Accept", "application/json")
208            .header(
209                "User-Agent",
210                format!(
211                    "nostr-rs-relay/{} NIP-05 Verifier",
212                    crate::info::CARGO_PKG_VERSION.unwrap()
213                ),
214            )
215            .body(hyper::Body::empty())
216            .expect("request builder");
217
218        let response_fut = self.client.request(req);
219
220        if let Ok(response_res) = tokio::time::timeout(Duration::from_secs(5), response_fut).await {
221            // limit size of verification document to 1MB.
222            const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024 * 1024;
223            let response = response_res?;
224            // determine content length from response
225            let response_content_length = match response.body().size_hint().upper() {
226                Some(v) => v,
227                None => MAX_ALLOWED_RESPONSE_SIZE + 1, // reject missing content length
228            };
229            // TODO: test how hyper handles the client providing an inaccurate content-length.
230            if response_content_length <= MAX_ALLOWED_RESPONSE_SIZE {
231                let (parts, body) = response.into_parts();
232                // TODO: consider redirects
233                if parts.status == http::StatusCode::OK {
234                    // parse body, determine if the username / key / address is present
235                    let body_bytes = hyper::body::to_bytes(body).await?;
236                    let body_matches = body_contains_user(&nip.local, pubkey, &body_bytes)?;
237                    if body_matches {
238                        return Ok(UserWebVerificationStatus::Verified);
239                    }
240                    // successful response, parsed as a nip-05
241                    // document, but this name/pubkey was not
242                    // present.
243                    return Ok(UserWebVerificationStatus::Unverified);
244                }
245            } else {
246                info!(
247                    "content length missing or exceeded limits for account: {:?}",
248                    nip.to_string()
249                );
250            }
251        } else {
252            info!("timeout verifying account {:?}", nip);
253            return Ok(UserWebVerificationStatus::Unknown);
254        }
255        Ok(UserWebVerificationStatus::Unknown)
256    }
257
258    /// Perform NIP-05 verifier tasks.
259    pub async fn run(&mut self) {
260        // use this to schedule periodic re-validation tasks
261        // run a loop, restarting on failure
262        loop {
263            let res = self.run_internal().await;
264            match res {
265                Err(Error::ChannelClosed) => {
266                    // channel was closed, we are shutting down
267                    return;
268                }
269                Err(e) => {
270                    info!("error in verifier: {:?}", e);
271                }
272                _ => {}
273            }
274        }
275    }
276
277    /// Internal select loop for performing verification
278    async fn run_internal(&mut self) -> Result<()> {
279        tokio::select! {
280            m = self.metadata_rx.recv() => {
281                match m {
282                    Ok(e) => {
283                        if let Some(naddr) = e.get_nip05_addr() {
284                            info!("got metadata event for ({:?},{:?})", naddr.to_string() ,e.get_author_prefix());
285                            // Process a new author, checking if they are verified:
286                            let check_verified = self.repo.get_latest_user_verification(&e.pubkey).await;
287                            // ensure the event we got is more recent than the one we have, otherwise we can ignore it.
288                            if let Ok(last_check) = check_verified {
289                                if e.created_at <= last_check.event_created {
290                                    // this metadata is from the same author as an existing verification.
291                                    // it is older than what we have, so we can ignore it.
292                                    debug!("received older metadata event for author {:?}", e.get_author_prefix());
293                                    return Ok(());
294                                }
295                            }
296                            // old, or no existing record for this user.  In either case, we just create a new one.
297                            let start = Instant::now();
298                            let v = self.get_web_verification_from_event(&e).await;
299                            info!(
300                                "checked name {:?}, result: {:?}, in: {:?}",
301                                naddr.to_string(),
302                                v,
303                                start.elapsed()
304                            );
305                            // sleep to limit how frequently we make HTTP requests for new metadata events.  This should limit us to 4 req/sec.
306                            tokio::time::sleep(Duration::from_millis(250)).await;
307                            // if this user was verified, we need to write the
308                            // record, persist the event, and broadcast.
309                            if let UserWebVerificationStatus::Verified = v {
310                                self.create_new_verified_user(&naddr.to_string(), &e).await?;
311                            }
312                        }
313                    },
314                    Err(tokio::sync::broadcast::error::RecvError::Lagged(c)) => {
315                        warn!("incoming metadata events overwhelmed buffer, {} events dropped",c);
316                    }
317                    Err(tokio::sync::broadcast::error::RecvError::Closed) => {
318                        info!("metadata broadcast channel closed");
319                        return Err(Error::ChannelClosed);
320                    }
321                }
322            },
323            _ = self.reverify_interval.tick() => {
324                // check and see if there is an old account that needs
325                // to be reverified
326                self.do_reverify().await?;
327            },
328        }
329        Ok(())
330    }
331
332    /// Reverify the oldest user verification record.
333    async fn do_reverify(&mut self) -> Result<()> {
334        let reverify_setting = self
335            .settings
336            .verified_users
337            .verify_update_frequency_duration;
338        let max_failures = self.settings.verified_users.max_consecutive_failures;
339        // get from settings, but default to 6hrs between re-checking an account
340        let reverify_dur = reverify_setting.unwrap_or_else(|| Duration::from_secs(60 * 60 * 6));
341        // find all verification records that have success or failure OLDER than the reverify_dur.
342        let now = SystemTime::now();
343        let earliest = now - reverify_dur;
344        let earliest_epoch = earliest
345            .duration_since(SystemTime::UNIX_EPOCH)
346            .map(|x| x.as_secs())
347            .unwrap_or(0);
348        let vr = self.repo.get_oldest_user_verification(earliest_epoch).await;
349        match vr {
350            Ok(ref v) => {
351                let new_status = self.get_web_verification(&v.name, &v.address).await;
352                match new_status {
353                    UserWebVerificationStatus::Verified => {
354                        // freshly verified account, update the
355                        // timestamp.
356                        self.repo.update_verification_timestamp(v.rowid).await?;
357                        info!("verification updated for {}", v.to_string());
358                    }
359                    UserWebVerificationStatus::DomainNotAllowed
360                    | UserWebVerificationStatus::Unknown => {
361                        // server may be offline, or temporarily
362                        // blocked by the config file.  Note the
363                        // failure so we can process something
364                        // else.
365
366                        // have we had enough failures to give up?
367                        if v.failure_count >= max_failures as u64 {
368                            info!(
369                                "giving up on verifying {:?} after {} failures",
370                                v.name, v.failure_count
371                            );
372                            self.repo.delete_verification(v.rowid).await?;
373                        } else {
374                            // record normal failure, incrementing failure count
375                            info!("verification failed for {}", v.to_string());
376                            self.repo.fail_verification(v.rowid).await?;
377                        }
378                    }
379                    UserWebVerificationStatus::Unverified => {
380                        // domain has removed the verification, drop
381                        // the record on our side.
382                        info!("verification rescinded for {}", v.to_string());
383                        self.repo.delete_verification(v.rowid).await?;
384                    }
385                }
386            }
387            Err(
388                Error::SqlError(rusqlite::Error::QueryReturnedNoRows)
389                | Error::SqlxError(sqlx::Error::RowNotFound),
390            ) => {
391                // No users need verification.  Reset the interval to
392                // the next verification attempt.
393                let start = tokio::time::Instant::now() + self.wait_after_finish;
394                self.reverify_interval = tokio::time::interval_at(start, self.http_wait_duration);
395            }
396            Err(ref e) => {
397                warn!(
398                    "Error when checking for NIP-05 verification records: {:?}",
399                    e
400                );
401            }
402        }
403        Ok(())
404    }
405
406    /// Persist an event, create a verification record, and broadcast.
407    // TODO: have more event-writing logic handled in the db module.
408    // Right now, these events avoid the rate limit.  That is
409    // acceptable since as soon as the user is registered, this path
410    // is no longer used.
411    // TODO: refactor these into spawn_blocking
412    // calls to get them off the async executors.
413    async fn create_new_verified_user(&mut self, name: &str, event: &Event) -> Result<()> {
414        let start = Instant::now();
415        // we should only do this if we are enabled.  if we are
416        // disabled/passive, the event has already been persisted.
417        let should_write_event = self.settings.verified_users.is_enabled();
418        if should_write_event {
419            match self.repo.write_event(event).await {
420                Ok(updated) => {
421                    if updated != 0 {
422                        info!(
423                            "persisted event (new verified pubkey): {:?} in {:?}",
424                            event.get_event_id_prefix(),
425                            start.elapsed()
426                        );
427                        self.event_tx.send(event.clone()).ok();
428                    }
429                }
430                Err(err) => {
431                    warn!("event insert failed: {:?}", err);
432                    if let Error::SqlError(r) = err {
433                        warn!("because: : {:?}", r);
434                    }
435                }
436            }
437        }
438        // write the verification record
439        self.repo
440            .create_verification_record(&event.id, name)
441            .await?;
442        Ok(())
443    }
444}
445
446/// Result of checking user's verification status against DNS/HTTP.
447#[derive(PartialEq, Eq, Debug, Clone)]
448pub enum UserWebVerificationStatus {
449    Verified,         // user is verified, as of now.
450    DomainNotAllowed, // domain blacklist or whitelist denied us from attempting a verification
451    Unknown,          // user's status could not be determined (timeout, server error)
452    Unverified,       // user's status is not verified (successful check, name / addr do not match)
453}
454
455/// A NIP-05 verification record.
456#[derive(PartialEq, Eq, Debug, Clone)]
457// Basic information for a verification event.  Gives us all we need to assert a NIP-05 address is good.
458pub struct VerificationRecord {
459    pub rowid: u64,                // database row for this verification event
460    pub name: Nip05Name,           // address being verified
461    pub address: String,           // pubkey
462    pub event: String,             // event ID hash providing the verification
463    pub event_created: u64,        // when the metadata event was published
464    pub last_success: Option<u64>, // the most recent time a verification was provided.  None if verification under this name has never succeeded.
465    pub last_failure: Option<u64>, // the most recent time verification was attempted, but could not be completed.
466    pub failure_count: u64,        // how many consecutive failures have been observed.
467}
468
469/// Check with settings to determine if a given domain is allowed to
470/// publish.
471#[must_use]
472pub fn is_domain_allowed(
473    domain: &str,
474    whitelist: &Option<Vec<String>>,
475    blacklist: &Option<Vec<String>>,
476) -> bool {
477    // if there is a whitelist, domain must be present in it.
478    if let Some(wl) = whitelist {
479        // workaround for Vec contains not accepting &str
480        return wl.iter().any(|x| x == domain);
481    }
482    // otherwise, check that user is not in the blacklist
483    if let Some(bl) = blacklist {
484        return !bl.iter().any(|x| x == domain);
485    }
486    true
487}
488
489impl VerificationRecord {
490    /// Check if the record is recent enough to be considered valid,
491    /// and the domain is allowed.
492    #[must_use]
493    pub fn is_valid(&self, verified_users_settings: &VerifiedUsers) -> bool {
494        //let settings = SETTINGS.read().unwrap();
495        // how long a verification record is good for
496        let nip05_expiration = &verified_users_settings.verify_expiration_duration;
497        if let Some(e) = nip05_expiration {
498            if !self.is_current(e) {
499                return false;
500            }
501        }
502        // check domains
503        is_domain_allowed(
504            &self.name.domain,
505            &verified_users_settings.domain_whitelist,
506            &verified_users_settings.domain_blacklist,
507        )
508    }
509
510    /// Check if this record has been validated since the given
511    /// duration.
512    fn is_current(&self, d: &Duration) -> bool {
513        match self.last_success {
514            Some(s) => {
515                // current time - duration
516                let now = SystemTime::now();
517                let cutoff = now - *d;
518                let cutoff_epoch = cutoff
519                    .duration_since(SystemTime::UNIX_EPOCH)
520                    .map(|x| x.as_secs())
521                    .unwrap_or(0);
522                s > cutoff_epoch
523            }
524            None => false,
525        }
526    }
527}
528
529impl std::fmt::Display for VerificationRecord {
530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531        write!(
532            f,
533            "({:?},{:?})",
534            self.name.to_string(),
535            self.address.chars().take(8).collect::<String>()
536        )
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543
544    #[test]
545    fn local_from_inet() {
546        let addr = "bob@example.com";
547        let parsed = Nip05Name::try_from(addr);
548        assert!(parsed.is_ok());
549        let v = parsed.unwrap();
550        assert_eq!(v.local, "bob");
551        assert_eq!(v.domain, "example.com");
552    }
553
554    #[test]
555    fn not_enough_sep() {
556        let addr = "bob_example.com";
557        let parsed = Nip05Name::try_from(addr);
558        assert!(parsed.is_err());
559    }
560
561    #[test]
562    fn too_many_sep() {
563        let addr = "foo@bob@example.com";
564        let parsed = Nip05Name::try_from(addr);
565        assert!(parsed.is_err());
566    }
567
568    #[test]
569    fn invalid_local_name() {
570        // non-permitted ascii chars
571        assert!(Nip05Name::try_from("foo!@example.com").is_err());
572        assert!(Nip05Name::try_from("foo @example.com").is_err());
573        assert!(Nip05Name::try_from(" foo@example.com").is_err());
574        assert!(Nip05Name::try_from("f oo@example.com").is_err());
575        assert!(Nip05Name::try_from("foo<@example.com").is_err());
576        // unicode dash
577        assert!(Nip05Name::try_from("foo‐bar@example.com").is_err());
578        // emoji
579        assert!(Nip05Name::try_from("foo😭bar@example.com").is_err());
580    }
581    #[test]
582    fn invalid_domain_name() {
583        // non-permitted ascii chars
584        assert!(Nip05Name::try_from("foo@examp!e.com").is_err());
585        assert!(Nip05Name::try_from("foo@ example.com").is_err());
586        assert!(Nip05Name::try_from("foo@exa mple.com").is_err());
587        assert!(Nip05Name::try_from("foo@example .com").is_err());
588        assert!(Nip05Name::try_from("foo@exa<mple.com").is_err());
589        // unicode dash
590        assert!(Nip05Name::try_from("foobar@exa‐mple.com").is_err());
591        // emoji
592        assert!(Nip05Name::try_from("foobar@ex😭ample.com").is_err());
593    }
594
595    #[test]
596    fn to_url() {
597        let nip = Nip05Name::try_from("foobar@example.com").unwrap();
598        assert_eq!(
599            nip.to_url(),
600            Some(
601                "https://example.com/.well-known/nostr.json?name=foobar"
602                    .parse()
603                    .unwrap()
604            )
605        );
606    }
607}