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
This commit is contained in:
Greg Heartsfield 2021-12-29 22:47:31 -06:00
parent d730bf0c59
commit f415295184
3 changed files with 49 additions and 10 deletions

View File

@ -1,4 +1,11 @@
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; 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)] #[derive(Debug, Serialize, Deserialize)]
#[allow(unused)] #[allow(unused)]
@ -7,6 +14,13 @@ pub struct Network {
pub address: String, 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)] #[derive(Debug, Serialize, Deserialize)]
#[allow(unused)] #[allow(unused)]
pub struct Retention { pub struct Retention {
@ -34,6 +48,7 @@ pub struct Settings {
pub network: Network, pub network: Network,
pub limits: Limits, pub limits: Limits,
pub retention: Retention, pub retention: Retention,
pub options: Options,
} }
impl Settings { impl Settings {
@ -76,6 +91,9 @@ impl Default for Settings {
persist_days: None, // oldest message persist_days: None, // oldest message
whitelist_addresses: vec![], // whitelisted addresses (never delete) whitelist_addresses: vec![], // whitelisted addresses (never delete)
}, },
options: Options {
reject_future_seconds: Some(30 * 60), // Reject events 30min in the future or greater
},
} }
} }
} }

View File

@ -1,4 +1,5 @@
//! Event parsing and validation //! Event parsing and validation
use crate::config;
use crate::error::Error::*; use crate::error::Error::*;
use crate::error::Result; use crate::error::Result;
use bitcoin_hashes::{sha256, Hash}; use bitcoin_hashes::{sha256, Hash};
@ -8,6 +9,7 @@ use serde::{Deserialize, Deserializer, Serialize};
use serde_json::value::Value; use serde_json::value::Value;
use serde_json::Number; use serde_json::Number;
use std::str::FromStr; use std::str::FromStr;
use std::time::SystemTime;
/// Event command in network format /// Event command in network format
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[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 { impl Event {
/// Create a short event identifier, suitable for logging. /// Create a short event identifier, suitable for logging.
pub fn get_event_id_prefix(&self) -> String { pub fn get_event_id_prefix(&self) -> String {
@ -64,6 +74,23 @@ impl Event {
/// Check if this event has a valid signature. /// Check if this event has a valid signature.
fn is_valid(&self) -> bool { 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: // validation is performed by:
// * parsing JSON string into event fields // * parsing JSON string into event fields
// * create an array: // * create an array:

View File

@ -1,7 +1,6 @@
//! Server process //! Server process
use futures::SinkExt; use futures::SinkExt;
use futures::StreamExt; use futures::StreamExt;
use lazy_static::lazy_static;
use log::*; use log::*;
use nostr_rs_relay::close::Close; use nostr_rs_relay::close::Close;
use nostr_rs_relay::config; 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::NostrMessage::*;
use nostr_rs_relay::protostream::NostrResponse::*; use nostr_rs_relay::protostream::NostrResponse::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::RwLock;
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::Builder; use tokio::runtime::Builder;
use tokio::sync::broadcast; use tokio::sync::broadcast;
@ -21,23 +19,19 @@ use tokio::sync::broadcast::{Receiver, Sender};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use tungstenite::protocol::WebSocketConfig; 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. /// Start running a Nostr relay server.
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
// setup logger // setup logger
let _ = env_logger::try_init(); 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 // replace default settings with those read from config.toml
let c = config::Settings::new(); let c = config::Settings::new();
debug!("using settings: {:?}", c); debug!("using settings: {:?}", c);
*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); let addr = format!("{}:{}", config.network.address.trim(), config.network.port);
// configure tokio runtime // configure tokio runtime
let rt = Builder::new_multi_thread() let rt = Builder::new_multi_thread()
@ -47,7 +41,7 @@ fn main() -> Result<(), Error> {
.unwrap(); .unwrap();
// start tokio // start tokio
rt.block_on(async { 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"); let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
info!("listening on: {}", addr); info!("listening on: {}", addr);
// all client-submitted valid events are broadcast to every // 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 bcast_rx = broadcast.subscribe();
let mut config = WebSocketConfig::default(); 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_message_size = settings.limits.max_ws_message_bytes;
config.max_frame_size = settings.limits.max_ws_frame_bytes; config.max_frame_size = settings.limits.max_ws_frame_bytes;
} }