nostr-rs-relay

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

commit f415295184ee748d0668e984a5827f4e2e74ff7e
parent d730bf0c59d22aa52e44bb722dccd23167d5b5b2
Author: Greg Heartsfield <scsibug@imap.cc>
Date:   Wed, 29 Dec 2021 22:47:31 -0600

feat: reject future-dated events

If configured, reject events than are more than N seconds in the
future.

Fixes https://todo.sr.ht/~gheartsfield/nostr-rs-relay/5

Diffstat:
Msrc/config.rs | 18++++++++++++++++++
Msrc/event.rs | 27+++++++++++++++++++++++++++
Msrc/main.rs | 14++++----------
3 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/src/config.rs b/src/config.rs @@ -1,4 +1,11 @@ +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use std::sync::RwLock; + +// initialize a singleton default configuration +lazy_static! { + pub static ref SETTINGS: RwLock<Settings> = RwLock::new(Settings::default()); +} #[derive(Debug, Serialize, Deserialize)] #[allow(unused)] @@ -7,6 +14,13 @@ pub struct Network { pub address: String, } +// +#[derive(Debug, Serialize, Deserialize)] +#[allow(unused)] +pub struct Options { + pub reject_future_seconds: Option<usize>, // if defined, reject any events with a timestamp more than X seconds in the future +} + #[derive(Debug, Serialize, Deserialize)] #[allow(unused)] pub struct Retention { @@ -34,6 +48,7 @@ pub struct Settings { pub network: Network, pub limits: Limits, pub retention: Retention, + pub options: Options, } impl Settings { @@ -76,6 +91,9 @@ impl Default for Settings { persist_days: None, // oldest message whitelist_addresses: vec![], // whitelisted addresses (never delete) }, + options: Options { + reject_future_seconds: Some(30 * 60), // Reject events 30min in the future or greater + }, } } } diff --git a/src/event.rs b/src/event.rs @@ -1,4 +1,5 @@ //! Event parsing and validation +use crate::config; use crate::error::Error::*; use crate::error::Result; use bitcoin_hashes::{sha256, Hash}; @@ -8,6 +9,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_json::value::Value; use serde_json::Number; use std::str::FromStr; +use std::time::SystemTime; /// Event command in network format #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] @@ -56,6 +58,14 @@ impl From<EventCmd> for Result<Event> { } } +/// Seconds since 1970 +fn unix_time() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|x| x.as_secs()) + .unwrap_or(0) +} + impl Event { /// Create a short event identifier, suitable for logging. pub fn get_event_id_prefix(&self) -> String { @@ -64,6 +74,23 @@ impl Event { /// Check if this event has a valid signature. fn is_valid(&self) -> bool { + // TODO: return a Result with a reason for invalid events + // don't bother to validate an event with a timestamp in the distant future. + let config = config::SETTINGS.read().unwrap(); + let max_future_sec = config.options.reject_future_seconds; + if max_future_sec.is_some() { + let allowable_future = max_future_sec.unwrap(); + let curr_time = unix_time(); + // calculate difference, plus how far future we allow + if curr_time + (allowable_future as u64) < self.created_at { + let delta = self.created_at - curr_time; + info!( + "Event is too far in the future ({} seconds), rejecting", + delta + ); + return false; + } + } // validation is performed by: // * parsing JSON string into event fields // * create an array: diff --git a/src/main.rs b/src/main.rs @@ -1,7 +1,6 @@ //! Server process use futures::SinkExt; use futures::StreamExt; -use lazy_static::lazy_static; use log::*; use nostr_rs_relay::close::Close; use nostr_rs_relay::config; @@ -13,7 +12,6 @@ use nostr_rs_relay::protostream; use nostr_rs_relay::protostream::NostrMessage::*; use nostr_rs_relay::protostream::NostrResponse::*; use std::collections::HashMap; -use std::sync::RwLock; use tokio::net::{TcpListener, TcpStream}; use tokio::runtime::Builder; use tokio::sync::broadcast; @@ -21,23 +19,19 @@ use tokio::sync::broadcast::{Receiver, Sender}; use tokio::sync::mpsc; use tokio::sync::oneshot; use tungstenite::protocol::WebSocketConfig; -// initialize a singleton default configuration -lazy_static! { - static ref SETTINGS: RwLock<config::Settings> = RwLock::new(config::Settings::default()); -} /// Start running a Nostr relay server. fn main() -> Result<(), Error> { // setup logger let _ = env_logger::try_init(); { - let mut settings = SETTINGS.write().unwrap(); + let mut settings = config::SETTINGS.write().unwrap(); // replace default settings with those read from config.toml let c = config::Settings::new(); debug!("using settings: {:?}", c); *settings = c; } - let config = SETTINGS.read().unwrap(); + let config = config::SETTINGS.read().unwrap(); let addr = format!("{}:{}", config.network.address.trim(), config.network.port); // configure tokio runtime let rt = Builder::new_multi_thread() @@ -47,7 +41,7 @@ fn main() -> Result<(), Error> { .unwrap(); // start tokio rt.block_on(async { - let settings = SETTINGS.read().unwrap(); + let settings = config::SETTINGS.read().unwrap(); let listener = TcpListener::bind(&addr).await.expect("Failed to bind"); info!("listening on: {}", addr); // all client-submitted valid events are broadcast to every @@ -109,7 +103,7 @@ async fn nostr_server( let mut bcast_rx = broadcast.subscribe(); let mut config = WebSocketConfig::default(); { - let settings = SETTINGS.read().unwrap(); + let settings = config::SETTINGS.read().unwrap(); config.max_message_size = settings.limits.max_ws_message_bytes; config.max_frame_size = settings.limits.max_ws_frame_bytes; }