diff --git a/flake.nix b/flake.nix index a0fed6d..80ce26b 100644 --- a/flake.nix +++ b/flake.nix @@ -50,6 +50,8 @@ env = { DATABASE_URL = "/tmp/sqlite.db"; ADMIN_PUBKEY = "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54"; + CONFIG_ENABLE_AUTH = "false"; + CONFIG_RELAY_URL = "ws://0.0.0.0:8080"; RUST_BACKTRACE = 1; RUST_LOG = "debug"; }; diff --git a/modules/sneedstr.nix b/modules/sneedstr.nix index 57e9d27..ded77a1 100644 --- a/modules/sneedstr.nix +++ b/modules/sneedstr.nix @@ -41,6 +41,10 @@ in { Local nixos-container ip address ''; }; + relayUrl = mkOption { + type = types.str; + description = "Relay URL that will be used for NIP-42 AUTH validation"; + }; }; config = mkIf cfg.enable { @@ -70,6 +74,7 @@ in { DATABASE_URL = "${DB_PATH}/sneedstr.db"; ADMIN_PUBKEY = cfg.adminPubkey; CONFIG_ENABLE_AUTH = cfg.enableAuth; + CONFIG_RELAY_URL = cfg.relayUrl; }; startLimitBurst = 1; startLimitIntervalSec = 10; @@ -117,6 +122,10 @@ in { proxyWebsockets = true; # needed if you need to use WebSocket recommendedProxySettings = true; }; + locations."/register" = { + proxyPass = "http://${cfg.localAddress}:8085"; + recommendedProxySettings = true; + }; }; }; }; diff --git a/src/bussy/mod.rs b/src/bussy/mod.rs index b5f899e..147d5d9 100644 --- a/src/bussy/mod.rs +++ b/src/bussy/mod.rs @@ -10,6 +10,7 @@ use tokio::sync::{broadcast, Mutex}; pub mod channels { pub static MSG_NOOSE: &str = "MSG_NOOSE"; pub static MSG_NIP05: &str = "MSG_NIP05"; + pub static MSG_AUTH: &str = "MSG_AUTH"; pub static MSG_RELAY: &str = "MSG_RELAY"; pub static MSG_PIPELINE: &str = "MSG_PIPELINE"; pub static MSG_SLED: &str = "MSG_SLED"; @@ -111,7 +112,7 @@ impl PubSub { } pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver { - let (tx, _rx) = broadcast::channel(32); // 32 is the channel capacity + let (tx, _rx) = broadcast::channel(256); // 256 is the channel capacity let mut subscribers = self.subscribers.lock().await; subscribers .entry(topic.to_string()) diff --git a/src/noose/pipeline.rs b/src/noose/pipeline.rs index 0b0e189..b3c24c6 100644 --- a/src/noose/pipeline.rs +++ b/src/noose/pipeline.rs @@ -22,7 +22,7 @@ impl Pipeline { let channel; let command = match message.content { Command::PipelineReqEvent(client_id, event) => { - match self.handle_event(client_id, event.clone()).await { + match self.handle_event(client_id, event).await { Ok(_) => { log::info!("[Pipeline] handle_event completed"); channel = message.source; diff --git a/src/noose/sqlite.rs b/src/noose/sqlite.rs index 77e037c..821faed 100644 --- a/src/noose/sqlite.rs +++ b/src/noose/sqlite.rs @@ -1487,7 +1487,7 @@ impl Noose for NostrSqlite { message, channel ); - + pubsub.publish(channel, message).await; } } @@ -1496,7 +1496,6 @@ impl Noose for NostrSqlite { } async fn write_event(&self, event: Box) -> Result { - // TODO: Maybe do event validation and admin deletions here match self.save_event(&event).await { Ok(status) => { let relay_message = nostr::RelayMessage::ok(event.id, status, ""); diff --git a/src/relay/ws.rs b/src/relay/ws.rs index cdcb0da..3c103cc 100644 --- a/src/relay/ws.rs +++ b/src/relay/ws.rs @@ -215,36 +215,60 @@ fn send(client: &Client, message: Message) { } } +fn needs_auth(context: &Context, client: &Client, event: Option<&Event>) -> bool { + if !context.config.auth_required() { + return false; + } + + if event.is_some() { + let event = event.unwrap(); + let admin_pk = context.config.get_admin_pubkey(); + if event.pubkey == *admin_pk { + return false; + } + + if event.kind() == nostr::Kind::Metadata { + return false; + } + } + + if !client.authenticated { + return true; + } + + false +} + async fn handle_msg(context: &Context, client: &mut Client, client_message: ClientMessage) { match client_message { ClientMessage::Event(event) => { - if context.config.auth_required() - && event.kind() != nostr::Kind::Metadata - && !client.authenticated - { + if needs_auth(context, client, Some(&event)) { request_auth(context, client).await; return; } + handle_event(context, client, event).await } ClientMessage::Req { subscription_id, filters, } => { - if context.config.auth_required() && !client.authenticated { + if needs_auth(context, client, None) { request_auth(context, client).await; return; } + handle_req(context, client, subscription_id, filters).await } ClientMessage::Count { subscription_id, filters, } => { - if context.config.auth_required() && !client.authenticated { + if needs_auth(context, client, None) { request_auth(context, client).await; return; } + handle_count(context, client, subscription_id, filters).await } ClientMessage::Close(subscription_id) => handle_close(client, subscription_id).await, @@ -378,9 +402,51 @@ async fn handle_close(client: &mut Client, subscription_id: SubscriptionId) { } async fn handle_auth(context: &Context, client: &mut Client, event: Box) { - client.authenticate(&event); - let client_status = format!("Client authenticated: {}", client.authenticated); - let message = nostr::RelayMessage::notice(client_status); + let mut subscriber = context.pubsub.subscribe(channels::MSG_AUTH).await; + context + .pubsub + .publish( + channels::MSG_NOOSE, + crate::bussy::Message { + source: channels::MSG_AUTH, + content: crate::bussy::Command::DbReqGetProfile(event.pubkey), + }, + ) + .await; + + let mut message = nostr::RelayMessage::ok( + event.id, + client.authenticated, + "auth-required: User not registered", + ); + + let Ok(result) = subscriber.recv().await else { + context + .pubsub + .publish( + channels::MSG_RELAY, + crate::bussy::Message { + source: channels::MSG_RELAY, + content: crate::bussy::Command::DbResOkWithStatus(client.client_id, message), + }, + ) + .await; + return; + }; + + if let crate::bussy::Command::DbResGetProfile(profile) = result.content { + client.authenticate(context.config.get_relay_url(), &event); + + let client_status = format!("Client authenticated: {}", client.authenticated); + let status_message = if client.authenticated { + "" + } else { + "auth-required: we only accept events from registered users" + }; + + message = nostr::RelayMessage::ok(event.id, client.authenticated, status_message); + }; + context .pubsub .publish( diff --git a/src/utils/config.rs b/src/utils/config.rs index f3d3d22..ea25463 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{path::PathBuf, str::FromStr}; use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey}; @@ -7,6 +7,7 @@ pub struct Config { admin_pubkey: XOnlyPublicKey, db_path: PathBuf, auth_required: bool, + relay_url: nostr::UncheckedUrl, } impl Default for Config { @@ -28,11 +29,13 @@ impl Config { .unwrap_or("false".to_string()) .parse() .unwrap(); + let relay_url = nostr::UncheckedUrl::from_str(&std::env::var("CONFIG_RELAY_URL").unwrap()).unwrap(); Self { admin_pubkey, db_path, auth_required, + relay_url, } } @@ -48,12 +51,16 @@ impl Config { self.db_path.clone() } + pub fn get_relay_url(&self) -> nostr::UncheckedUrl { + self.relay_url.clone() + } + pub fn get_relay_config_json(&self) -> serde_json::Value { serde_json::json!({ "contact": "klink@zhitno.st", "name": "zhitno.st", "description": "Very *special* nostr relay", - "supported_nips": [ 1, 2, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 42, 45, 50 ], + "supported_nips": [ 1, 2, 4, 9, 11, 12, 15, 16, 20, 22, 28, 33, 40, 42, 45, 50 ], "software": "git+https://git.zhitno.st/Klink/sneedstr.git", "version": "0.1.1" }) diff --git a/src/utils/structs.rs b/src/utils/structs.rs index cd90fdb..48863d3 100644 --- a/src/utils/structs.rs +++ b/src/utils/structs.rs @@ -77,13 +77,13 @@ impl Client { self.challlenge = Some(challenge); } - pub fn authenticate(&mut self, event: &nostr::Event) { + pub fn authenticate(&mut self, relay_url: nostr::UncheckedUrl, event: &nostr::Event) { if self.challlenge.is_none() { return; } let challenge_tag = nostr::Tag::Challenge(self.challlenge.clone().unwrap()); - let relay_tag = nostr::Tag::Relay(nostr::UncheckedUrl::from_str("ws://0.0.0.0:8080").unwrap()); // TODO: Use relay address from env variable + let relay_tag = nostr::Tag::Relay(relay_url); if let Ok(()) = event.verify() { log::debug!("event is valid"); if event.kind.as_u32() == 22242 && event.created_at() > nostr::Timestamp::from(nostr::Timestamp::now().as_u64() - 600) {