From a1c81acd8f0d3864bd1fe83cba5ea3b23e4956b8 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sun, 18 Feb 2024 10:02:10 +1300 Subject: [PATCH] Restructure handling code to ensure EVENT always gets an OK reply (except for early errors) --- src/error.rs | 12 ++++++++ src/nostr.rs | 77 +++++++++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/error.rs b/src/error.rs index e99b288..e4d54d6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,9 @@ pub enum ChorusError { // Auth failure AuthFailure, + // Auth required + AuthRequired, + // Bad event id BadEventId, @@ -52,6 +55,9 @@ pub enum ChorusError { // End of Input EndOfInput, + // Event is Invalid + EventIsInvalid(String), + // Http Http(hyper::http::Error), @@ -88,6 +94,9 @@ pub enum ChorusError { // No private key NoPrivateKey, + // Restricted + Restricted, + // Rustls Rustls(tokio_rustls::rustls::Error), @@ -114,6 +123,7 @@ impl std::fmt::Display for ChorusError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ChorusError::AuthFailure => write!(f, "AUTH failure"), + ChorusError::AuthRequired => write!(f, "AUTH required"), ChorusError::BadEventId => write!(f, "Bad event id, does not match hash"), ChorusError::BadHexInput => write!(f, "Bad hex input"), ChorusError::BufferTooSmall => write!(f, "Output buffer too small"), @@ -123,6 +133,7 @@ impl std::fmt::Display for ChorusError { ChorusError::Crypto(e) => write!(f, "{e}"), ChorusError::Duplicate => write!(f, "Duplicate"), ChorusError::EndOfInput => write!(f, "End of input"), + ChorusError::EventIsInvalid(s) => write!(f, "Event is invalid: {s}"), ChorusError::Http(e) => write!(f, "{e}"), ChorusError::Hyper(e) => write!(f, "{e}"), ChorusError::Io(e) => write!(f, "{e}"), @@ -147,6 +158,7 @@ impl std::fmt::Display for ChorusError { ), ChorusError::Lmdb(e) => write!(f, "{e}"), ChorusError::NoPrivateKey => write!(f, "Private Key Not Found"), + ChorusError::Restricted => write!(f, "Restricted"), ChorusError::Rustls(e) => write!(f, "{e}"), ChorusError::Tungstenite(e) => write!(f, "{e}"), ChorusError::Scraper => write!(f, "Filter is underspecified. Scrapers are not allowed"), diff --git a/src/nostr.rs b/src/nostr.rs index e67eca9..6601abf 100644 --- a/src/nostr.rs +++ b/src/nostr.rs @@ -115,6 +115,8 @@ impl WebSocketService { } pub async fn event(&mut self, msg: String, mut inpos: usize) -> Result<(), Error> { + const PERSONAL_MSG: &str = "this personal relay only accepts events related to its users"; + let input = msg.as_bytes(); eat_whitespace(input, &mut inpos); @@ -123,54 +125,61 @@ impl WebSocketService { // Read the event into the session buffer let (_incount, event) = Event::from_json(&input[inpos..], &mut self.buffer)?; + let id = event.id(); + + let reply = match self.event_inner().await { + Ok(()) => NostrReply::Ok(id, true, NostrReplyPrefix::None, "".to_string()), + Err(e) => match e.inner { + ChorusError::AuthRequired => NostrReply::Ok( + id, + false, + NostrReplyPrefix::AuthRequired, + PERSONAL_MSG.to_owned(), + ), + ChorusError::Duplicate => { + NostrReply::Ok(id, false, NostrReplyPrefix::Duplicate, "".to_string()) + } + ChorusError::EventIsInvalid(why) => { + NostrReply::Ok(id, false, NostrReplyPrefix::Invalid, why) + } + ChorusError::Restricted => NostrReply::Ok( + id, + false, + NostrReplyPrefix::Restricted, + PERSONAL_MSG.to_owned(), + ), + _ => NostrReply::Ok(id, false, NostrReplyPrefix::Error, format!("{}", e)), + }, + }; + self.websocket.send(Message::text(reply.as_json())).await?; + + Ok(()) + } + + async fn event_inner(&mut self) -> Result<(), Error> { + // Delineate the event back out of the session buffer + let event = Event::delineate(&self.buffer)?; if GLOBALS.config.read().await.verify_events { // Verify the event is valid (id is hash, signature is valid) if let Err(e) = event.verify() { - let reply = NostrReply::Ok( - event.id(), - false, - NostrReplyPrefix::Invalid, - format!("{}", e), - ); - self.websocket.send(Message::text(reply.as_json())).await?; - return Ok(()); + return Err(ChorusError::EventIsInvalid(format!("{}", e)).into()); } } // Screen the event to see if we are willing to accept it if !screen_event(&event, self.user).await? { - let prefix = if self.user.is_some() { - NostrReplyPrefix::Restricted + if self.user.is_some() { + return Err(ChorusError::Restricted.into()); } else { - NostrReplyPrefix::AuthRequired - }; - let reply = NostrReply::Ok( - event.id(), - false, - prefix, - "this personal relay only accepts events related to its users".to_owned(), - ); - self.websocket.send(Message::text(reply.as_json())).await?; - return Ok(()); + return Err(ChorusError::AuthRequired.into()); + } } // Store and index the event - let reply = match GLOBALS.store.get().unwrap().store_event(&event) { - Ok(offset) => { - GLOBALS.new_events.send(offset)?; // advertise the new event - NostrReply::Ok(event.id(), true, NostrReplyPrefix::None, "".to_owned()) - } - Err(e) => { - if matches!(e.inner, ChorusError::Duplicate) { - NostrReply::Ok(event.id(), true, NostrReplyPrefix::Duplicate, "".to_owned()) - } else { - NostrReply::Ok(event.id(), false, NostrReplyPrefix::Error, format!("{e}")) - } - } - }; + let offset = GLOBALS.store.get().unwrap().store_event(&event)?; + GLOBALS.new_events.send(offset)?; // advertise the new event - self.websocket.send(Message::text(reply.as_json())).await?; Ok(()) }