1use 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
22pub struct Verifier {
24 repo: Arc<dyn NostrRepo>,
26 metadata_rx: tokio::sync::broadcast::Receiver<Event>,
28 event_tx: tokio::sync::broadcast::Sender<Event>,
30 settings: crate::config::Settings,
32 client: hyper::Client<HttpsConnector<HttpConnector>, hyper::Body>,
34 wait_after_finish: Duration,
36 http_wait_duration: Duration,
38 reverify_interval: Interval,
40}
41
42#[derive(PartialEq, Eq, Debug, Clone)]
44pub struct Nip05Name {
45 pub local: String,
46 pub domain: String,
47}
48
49impl Nip05Name {
50 #[must_use]
52 pub fn is_domain_only(&self) -> bool {
53 self.local == "_"
54 }
55
56 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
67impl std::convert::TryFrom<&str> for Nip05Name {
69 type Error = Error;
70 fn try_from(inet: &str) -> Result<Self, Self::Error> {
71 let components: Vec<&str> = inet.split('@').collect();
73 if components.len() == 2 {
74 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
111fn body_contains_user(username: &str, address: &str, bytes: &hyper::body::Bytes) -> Result<bool> {
113 let body: serde_json::Value = serde_json::from_slice(bytes)?;
115 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 let check_name = names_map.get(username).and_then(serde_json::Value::as_str);
123 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 let https = HttpsConnector::new();
137 let client = Client::builder().build::<_, hyper::Body>(https);
138
139 let wait_after_finish = Duration::from_secs(60 * 10);
142 let http_wait_duration = Duration::from_secs(1);
145 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 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 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 async fn get_web_verification_res(
189 &mut self,
190 nip: &Nip05Name,
191 pubkey: &str,
192 ) -> Result<UserWebVerificationStatus> {
193 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 const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024 * 1024;
223 let response = response_res?;
224 let response_content_length = match response.body().size_hint().upper() {
226 Some(v) => v,
227 None => MAX_ALLOWED_RESPONSE_SIZE + 1, };
229 if response_content_length <= MAX_ALLOWED_RESPONSE_SIZE {
231 let (parts, body) = response.into_parts();
232 if parts.status == http::StatusCode::OK {
234 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 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 pub async fn run(&mut self) {
260 loop {
263 let res = self.run_internal().await;
264 match res {
265 Err(Error::ChannelClosed) => {
266 return;
268 }
269 Err(e) => {
270 info!("error in verifier: {:?}", e);
271 }
272 _ => {}
273 }
274 }
275 }
276
277 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 let check_verified = self.repo.get_latest_user_verification(&e.pubkey).await;
287 if let Ok(last_check) = check_verified {
289 if e.created_at <= last_check.event_created {
290 debug!("received older metadata event for author {:?}", e.get_author_prefix());
293 return Ok(());
294 }
295 }
296 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 tokio::time::sleep(Duration::from_millis(250)).await;
307 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 self.do_reverify().await?;
327 },
328 }
329 Ok(())
330 }
331
332 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 let reverify_dur = reverify_setting.unwrap_or_else(|| Duration::from_secs(60 * 60 * 6));
341 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 self.repo.update_verification_timestamp(v.rowid).await?;
357 info!("verification updated for {}", v.to_string());
358 }
359 UserWebVerificationStatus::DomainNotAllowed
360 | UserWebVerificationStatus::Unknown => {
361 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 info!("verification failed for {}", v.to_string());
376 self.repo.fail_verification(v.rowid).await?;
377 }
378 }
379 UserWebVerificationStatus::Unverified => {
380 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 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 async fn create_new_verified_user(&mut self, name: &str, event: &Event) -> Result<()> {
414 let start = Instant::now();
415 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 self.repo
440 .create_verification_record(&event.id, name)
441 .await?;
442 Ok(())
443 }
444}
445
446#[derive(PartialEq, Eq, Debug, Clone)]
448pub enum UserWebVerificationStatus {
449 Verified, DomainNotAllowed, Unknown, Unverified, }
454
455#[derive(PartialEq, Eq, Debug, Clone)]
457pub struct VerificationRecord {
459 pub rowid: u64, pub name: Nip05Name, pub address: String, pub event: String, pub event_created: u64, pub last_success: Option<u64>, pub last_failure: Option<u64>, pub failure_count: u64, }
468
469#[must_use]
472pub fn is_domain_allowed(
473 domain: &str,
474 whitelist: &Option<Vec<String>>,
475 blacklist: &Option<Vec<String>>,
476) -> bool {
477 if let Some(wl) = whitelist {
479 return wl.iter().any(|x| x == domain);
481 }
482 if let Some(bl) = blacklist {
484 return !bl.iter().any(|x| x == domain);
485 }
486 true
487}
488
489impl VerificationRecord {
490 #[must_use]
493 pub fn is_valid(&self, verified_users_settings: &VerifiedUsers) -> bool {
494 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 is_domain_allowed(
504 &self.name.domain,
505 &verified_users_settings.domain_whitelist,
506 &verified_users_settings.domain_blacklist,
507 )
508 }
509
510 fn is_current(&self, d: &Duration) -> bool {
513 match self.last_success {
514 Some(s) => {
515 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 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 assert!(Nip05Name::try_from("foo‐bar@example.com").is_err());
578 assert!(Nip05Name::try_from("foo😭bar@example.com").is_err());
580 }
581 #[test]
582 fn invalid_domain_name() {
583 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 assert!(Nip05Name::try_from("foobar@exa‐mple.com").is_err());
591 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}