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 }