Finished NIP42 implementation

This commit is contained in:
Tony Klink 2024-02-13 09:53:41 -06:00
parent f7b74bd22c
commit d06206bb24
Signed by: klink
GPG key ID: 85175567C4D19231
8 changed files with 101 additions and 17 deletions

View file

@ -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";
}; };

View file

@ -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;
};
}; };
}; };
}; };

View file

@ -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())

View file

@ -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;

View file

@ -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, "");

View file

@ -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(

View file

@ -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"
}) })

View file

@ -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) {