Finished NIP42 implementation
This commit is contained in:
		
							parent
							
								
									f7b74bd22c
								
							
						
					
					
						commit
						d06206bb24
					
				
					 8 changed files with 101 additions and 17 deletions
				
			
		| 
						 | 
					@ -50,6 +50,8 @@
 | 
				
			||||||
          env = {
 | 
					          env = {
 | 
				
			||||||
            DATABASE_URL = "/tmp/sqlite.db";
 | 
					            DATABASE_URL = "/tmp/sqlite.db";
 | 
				
			||||||
            ADMIN_PUBKEY = "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54";
 | 
					            ADMIN_PUBKEY = "npub1m2w0ckmkgj4wtvl8muwjynh56j3qd4nddca4exdg4mdrkepvfnhsmusy54";
 | 
				
			||||||
 | 
					            CONFIG_ENABLE_AUTH = "false";
 | 
				
			||||||
 | 
					            CONFIG_RELAY_URL = "ws://0.0.0.0:8080";
 | 
				
			||||||
            RUST_BACKTRACE = 1;
 | 
					            RUST_BACKTRACE = 1;
 | 
				
			||||||
            RUST_LOG = "debug";
 | 
					            RUST_LOG = "debug";
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +41,10 @@ in {
 | 
				
			||||||
        Local nixos-container ip address
 | 
					        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 {
 | 
					  config = mkIf cfg.enable {
 | 
				
			||||||
| 
						 | 
					@ -70,6 +74,7 @@ in {
 | 
				
			||||||
            DATABASE_URL = "${DB_PATH}/sneedstr.db";
 | 
					            DATABASE_URL = "${DB_PATH}/sneedstr.db";
 | 
				
			||||||
            ADMIN_PUBKEY = cfg.adminPubkey;
 | 
					            ADMIN_PUBKEY = cfg.adminPubkey;
 | 
				
			||||||
            CONFIG_ENABLE_AUTH = cfg.enableAuth;
 | 
					            CONFIG_ENABLE_AUTH = cfg.enableAuth;
 | 
				
			||||||
 | 
					            CONFIG_RELAY_URL = cfg.relayUrl;
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
          startLimitBurst = 1;
 | 
					          startLimitBurst = 1;
 | 
				
			||||||
          startLimitIntervalSec = 10;
 | 
					          startLimitIntervalSec = 10;
 | 
				
			||||||
| 
						 | 
					@ -117,6 +122,10 @@ in {
 | 
				
			||||||
          proxyWebsockets = true; # needed if you need to use WebSocket
 | 
					          proxyWebsockets = true; # needed if you need to use WebSocket
 | 
				
			||||||
          recommendedProxySettings = true;
 | 
					          recommendedProxySettings = true;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					        locations."/register" = {
 | 
				
			||||||
 | 
					          proxyPass = "http://${cfg.localAddress}:8085";
 | 
				
			||||||
 | 
					          recommendedProxySettings = true;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ use tokio::sync::{broadcast, Mutex};
 | 
				
			||||||
pub mod channels {
 | 
					pub mod channels {
 | 
				
			||||||
    pub static MSG_NOOSE: &str = "MSG_NOOSE";
 | 
					    pub static MSG_NOOSE: &str = "MSG_NOOSE";
 | 
				
			||||||
    pub static MSG_NIP05: &str = "MSG_NIP05";
 | 
					    pub static MSG_NIP05: &str = "MSG_NIP05";
 | 
				
			||||||
 | 
					    pub static MSG_AUTH: &str = "MSG_AUTH";
 | 
				
			||||||
    pub static MSG_RELAY: &str = "MSG_RELAY";
 | 
					    pub static MSG_RELAY: &str = "MSG_RELAY";
 | 
				
			||||||
    pub static MSG_PIPELINE: &str = "MSG_PIPELINE";
 | 
					    pub static MSG_PIPELINE: &str = "MSG_PIPELINE";
 | 
				
			||||||
    pub static MSG_SLED: &str = "MSG_SLED";
 | 
					    pub static MSG_SLED: &str = "MSG_SLED";
 | 
				
			||||||
| 
						 | 
					@ -111,7 +112,7 @@ impl PubSub {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver<Message> {
 | 
					    pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver<Message> {
 | 
				
			||||||
        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;
 | 
					        let mut subscribers = self.subscribers.lock().await;
 | 
				
			||||||
        subscribers
 | 
					        subscribers
 | 
				
			||||||
            .entry(topic.to_string())
 | 
					            .entry(topic.to_string())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ impl Pipeline {
 | 
				
			||||||
            let channel;
 | 
					            let channel;
 | 
				
			||||||
            let command = match message.content {
 | 
					            let command = match message.content {
 | 
				
			||||||
                Command::PipelineReqEvent(client_id, event) => {
 | 
					                Command::PipelineReqEvent(client_id, event) => {
 | 
				
			||||||
                    match self.handle_event(client_id, event.clone()).await {
 | 
					                    match self.handle_event(client_id, event).await {
 | 
				
			||||||
                        Ok(_) => {
 | 
					                        Ok(_) => {
 | 
				
			||||||
                            log::info!("[Pipeline] handle_event completed");
 | 
					                            log::info!("[Pipeline] handle_event completed");
 | 
				
			||||||
                            channel = message.source;
 | 
					                            channel = message.source;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1496,7 +1496,6 @@ impl Noose for NostrSqlite {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn write_event(&self, event: Box<nostr::Event>) -> Result<nostr::RelayMessage, Error> {
 | 
					    async fn write_event(&self, event: Box<nostr::Event>) -> Result<nostr::RelayMessage, Error> {
 | 
				
			||||||
        // TODO: Maybe do event validation and admin deletions here
 | 
					 | 
				
			||||||
        match self.save_event(&event).await {
 | 
					        match self.save_event(&event).await {
 | 
				
			||||||
            Ok(status) => {
 | 
					            Ok(status) => {
 | 
				
			||||||
                let relay_message = nostr::RelayMessage::ok(event.id, status, "");
 | 
					                let relay_message = nostr::RelayMessage::ok(event.id, status, "");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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) {
 | 
					async fn handle_msg(context: &Context, client: &mut Client, client_message: ClientMessage) {
 | 
				
			||||||
    match client_message {
 | 
					    match client_message {
 | 
				
			||||||
        ClientMessage::Event(event) => {
 | 
					        ClientMessage::Event(event) => {
 | 
				
			||||||
            if context.config.auth_required()
 | 
					            if needs_auth(context, client, Some(&event)) {
 | 
				
			||||||
                && event.kind() != nostr::Kind::Metadata
 | 
					 | 
				
			||||||
                && !client.authenticated
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                request_auth(context, client).await;
 | 
					                request_auth(context, client).await;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            handle_event(context, client, event).await
 | 
					            handle_event(context, client, event).await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        ClientMessage::Req {
 | 
					        ClientMessage::Req {
 | 
				
			||||||
            subscription_id,
 | 
					            subscription_id,
 | 
				
			||||||
            filters,
 | 
					            filters,
 | 
				
			||||||
        } => {
 | 
					        } => {
 | 
				
			||||||
            if context.config.auth_required() && !client.authenticated {
 | 
					            if needs_auth(context, client, None) {
 | 
				
			||||||
                request_auth(context, client).await;
 | 
					                request_auth(context, client).await;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            handle_req(context, client, subscription_id, filters).await
 | 
					            handle_req(context, client, subscription_id, filters).await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        ClientMessage::Count {
 | 
					        ClientMessage::Count {
 | 
				
			||||||
            subscription_id,
 | 
					            subscription_id,
 | 
				
			||||||
            filters,
 | 
					            filters,
 | 
				
			||||||
        } => {
 | 
					        } => {
 | 
				
			||||||
            if context.config.auth_required() && !client.authenticated {
 | 
					            if needs_auth(context, client, None) {
 | 
				
			||||||
                request_auth(context, client).await;
 | 
					                request_auth(context, client).await;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            handle_count(context, client, subscription_id, filters).await
 | 
					            handle_count(context, client, subscription_id, filters).await
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        ClientMessage::Close(subscription_id) => handle_close(client, subscription_id).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<Event>) {
 | 
					async fn handle_auth(context: &Context, client: &mut Client, event: Box<Event>) {
 | 
				
			||||||
    client.authenticate(&event);
 | 
					    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 client_status = format!("Client authenticated: {}", client.authenticated);
 | 
				
			||||||
    let message = nostr::RelayMessage::notice(client_status);
 | 
					        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
 | 
					    context
 | 
				
			||||||
        .pubsub
 | 
					        .pubsub
 | 
				
			||||||
        .publish(
 | 
					        .publish(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
use std::path::PathBuf;
 | 
					use std::{path::PathBuf, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
					use nostr::{key::FromPkStr, secp256k1::XOnlyPublicKey};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ pub struct Config {
 | 
				
			||||||
    admin_pubkey: XOnlyPublicKey,
 | 
					    admin_pubkey: XOnlyPublicKey,
 | 
				
			||||||
    db_path: PathBuf,
 | 
					    db_path: PathBuf,
 | 
				
			||||||
    auth_required: bool,
 | 
					    auth_required: bool,
 | 
				
			||||||
 | 
					    relay_url: nostr::UncheckedUrl,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Config {
 | 
					impl Default for Config {
 | 
				
			||||||
| 
						 | 
					@ -28,11 +29,13 @@ impl Config {
 | 
				
			||||||
            .unwrap_or("false".to_string())
 | 
					            .unwrap_or("false".to_string())
 | 
				
			||||||
            .parse()
 | 
					            .parse()
 | 
				
			||||||
            .unwrap();
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        let relay_url = nostr::UncheckedUrl::from_str(&std::env::var("CONFIG_RELAY_URL").unwrap()).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            admin_pubkey,
 | 
					            admin_pubkey,
 | 
				
			||||||
            db_path,
 | 
					            db_path,
 | 
				
			||||||
            auth_required,
 | 
					            auth_required,
 | 
				
			||||||
 | 
					            relay_url,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,12 +51,16 @@ impl Config {
 | 
				
			||||||
        self.db_path.clone()
 | 
					        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 {
 | 
					    pub fn get_relay_config_json(&self) -> serde_json::Value {
 | 
				
			||||||
        serde_json::json!({
 | 
					        serde_json::json!({
 | 
				
			||||||
            "contact": "klink@zhitno.st",
 | 
					            "contact": "klink@zhitno.st",
 | 
				
			||||||
            "name": "zhitno.st",
 | 
					            "name": "zhitno.st",
 | 
				
			||||||
            "description": "Very *special* nostr relay",
 | 
					            "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",
 | 
					            "software": "git+https://git.zhitno.st/Klink/sneedstr.git",
 | 
				
			||||||
            "version": "0.1.1"
 | 
					            "version": "0.1.1"
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,13 +77,13 @@ impl Client {
 | 
				
			||||||
        self.challlenge = Some(challenge);
 | 
					        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() {
 | 
					        if self.challlenge.is_none() {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let challenge_tag = nostr::Tag::Challenge(self.challlenge.clone().unwrap());
 | 
					        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() {
 | 
					        if let Ok(()) = event.verify() {
 | 
				
			||||||
            log::debug!("event is valid");
 | 
					            log::debug!("event is valid");
 | 
				
			||||||
            if event.kind.as_u32() == 22242 && event.created_at() > nostr::Timestamp::from(nostr::Timestamp::now().as_u64() - 600) {
 | 
					            if event.kind.as_u32() == 22242 && event.created_at() > nostr::Timestamp::from(nostr::Timestamp::now().as_u64() - 600) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue