From d3da4eb009ff7266edd67764f1aeeffe3513360c Mon Sep 17 00:00:00 2001 From: Greg Heartsfield Date: Mon, 3 Jan 2022 18:42:24 -0500 Subject: [PATCH] feat: implementation of proposed NIP-11 (server metadata) --- Cargo.lock | 1 + Cargo.toml | 2 +- config.toml | 12 ++++++++++ src/config.rs | 17 ++++++++++++++ src/info.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 29 ++++++++++++++++++++---- 7 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/info.rs diff --git a/Cargo.lock b/Cargo.lock index 2edc37f..cb0eb4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1097,6 +1097,7 @@ version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" dependencies = [ + "indexmap", "itoa", "ryu", "serde 1.0.131", diff --git a/Cargo.toml b/Cargo.toml index 01d88f5..eeea0b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ config = { version = "0.11", features = ["toml"] } bitcoin_hashes = { version = "^0.9", features = ["serde"] } secp256k1 = {git = "https://github.com/rust-bitcoin/rust-secp256k1.git", rev = "50034ccb18fdd84904ab3aa6c84a12fcced33209", features = ["rand", "rand-std", "serde", "bitcoin_hashes"] } serde = { version = "^1.0", features = ["derive"] } -serde_json = "^1.0" +serde_json = {version = "^1.0", features = ["preserve_order"]} hex = "^0.4" rusqlite = "^0.26" lazy_static = "^1.4" diff --git a/config.toml b/config.toml index 3b3a99d..3b55435 100644 --- a/config.toml +++ b/config.toml @@ -1,4 +1,16 @@ # Nostr-rs-relay configuration + +[info] +# Relay information for clients. Put your unique server name here. +name = "nostr-rs-relay" +# Description +description = "A newly created nostr-rs-relay.\n\nCustomize this with your own info." +# Administrative contact pubkey +#pubkey = "0c2d168a4ae8ca58c9f1ab237b5df682599c6c7ab74307ea8b05684b60405d41" + +# Administrative contact email +#email = "contact@example.com" + [database] # Directory for SQLite files. Defaults to the current directory. Can # also be specified (and overriden) with the "--db dirname" command diff --git a/src/config.rs b/src/config.rs index 3cbb784..d74fa02 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,16 @@ lazy_static! { pub static ref SETTINGS: RwLock = RwLock::new(Settings::default()); } +#[derive(Debug, Serialize, Deserialize)] +#[allow(unused)] +pub struct Info { + pub name: Option, + #[serde(rename = "description")] + pub descr: Option, + pub pubkey: Option, + pub email: Option, +} + #[derive(Debug, Serialize, Deserialize)] #[allow(unused)] pub struct Database { @@ -52,6 +62,7 @@ pub struct Limits { #[derive(Debug, Serialize, Deserialize)] #[allow(unused)] pub struct Settings { + pub info: Info, pub database: Database, pub network: Network, pub limits: Limits, @@ -89,6 +100,12 @@ impl Settings { impl Default for Settings { fn default() -> Self { Settings { + info: Info { + name: Some("Unnamed nostr-rs-relay".to_owned()), + descr: None, + pubkey: None, + email: None, + }, database: Database { data_directory: ".".to_owned(), }, diff --git a/src/info.rs b/src/info.rs new file mode 100644 index 0000000..6e6a0e0 --- /dev/null +++ b/src/info.rs @@ -0,0 +1,61 @@ +use crate::config; +/// Relay Info +use serde::{Deserialize, Serialize}; +use serde_json::value::Value; + +const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); + +#[derive(Debug, Serialize, Deserialize)] +#[allow(unused)] +pub struct RelayInfo { + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub descr: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pubkey: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub supported_nips: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub software: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +impl Default for RelayInfo { + fn default() -> Self { + RelayInfo { + name: None, + descr: None, + pubkey: None, + email: None, + supported_nips: Some(vec!["NIP-01".to_owned()]), + software: Some("https://git.sr.ht/~gheartsfield/nostr-rs-relay".to_owned()), + version: CARGO_PKG_VERSION.map(|x| x.to_owned()), + } + } +} + +/// Convert an Info struct into Relay Info json string +pub fn relay_info_json(info: &config::Info) -> String { + // get a default RelayInfo + let mut r = RelayInfo::default(); + // update fields from Info, if present + r.name = info.name.clone(); + r.descr = info.descr.clone(); + r.pubkey = info.pubkey.clone(); + r.email = info.email.clone(); + r.to_json() +} + +impl RelayInfo { + pub fn to_json(self) -> String { + // create the info ARRAY + let mut info_arr: Vec = vec![]; + info_arr.push(Value::String("NOSTR_SERVER_INFO".to_owned())); + info_arr.push(serde_json::to_value(&self).unwrap()); + serde_json::to_string_pretty(&info_arr).unwrap() + } +} diff --git a/src/lib.rs b/src/lib.rs index 1be173c..741528f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,6 @@ pub mod conn; pub mod db; pub mod error; pub mod event; +pub mod info; pub mod protostream; pub mod subscription; diff --git a/src/main.rs b/src/main.rs index 6907a93..a2d0252 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ //! Server process use futures::SinkExt; use futures::StreamExt; +use hyper::header::ACCEPT; use hyper::service::{make_service_fn, service_fn}; use hyper::upgrade::Upgraded; use hyper::{ @@ -13,6 +14,7 @@ use nostr_rs_relay::conn; use nostr_rs_relay::db; use nostr_rs_relay::error::{Error, Result}; use nostr_rs_relay::event::Event; +use nostr_rs_relay::info::relay_info_json; use nostr_rs_relay::protostream; use nostr_rs_relay::protostream::NostrMessage::*; use nostr_rs_relay::protostream::NostrResponse::*; @@ -47,7 +49,7 @@ async fn handle_web_request( request.uri().path(), request.headers().contains_key(header::UPGRADE), ) { - //if the request is ws_echo and the request headers contains an Upgrade key + // Request for / as websocket ("/", true) => { debug!("websocket with upgrade request"); //assume request is a handshake, so create the handshake response @@ -96,11 +98,30 @@ async fn handle_web_request( }; Ok::<_, Infallible>(response) } + // Request for Relay info ("/", false) => { // handle request at root with no upgrade header - Ok(Response::new(Body::from( - "This is a Nostr relay.\n".to_string(), - ))) + // Check if this is a nostr server info request + let accept_header = &request.headers().get(ACCEPT); + // check if application/nostr+json is included + if let Some(media_types) = accept_header { + if let Ok(mt_str) = media_types.to_str() { + if mt_str.contains("application/nostr+json") { + let config = config::SETTINGS.read().unwrap(); + // build a relay info response + debug!("Responding to server info request"); + let b = Body::from(relay_info_json(&config.info)); + return Ok(Response::builder() + .status(200) + .header("Content-Type", "application/nostr+json") + .body(b) + .unwrap()); + } + } + } + return Ok(Response::new(Body::from( + "Please use a Nostr client to connect.", + ))); } (_, _) => { //handle any other url