Finished NIP42 implementation
This commit is contained in:
parent
f7b74bd22c
commit
d06206bb24
|
@ -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…
Reference in a new issue