Skip to content

pool: add AuthenticationMiddleware (WIP) #872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion crates/nostr-relay-pool/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use std::fmt;

use nostr::util::BoxedFuture;
use nostr::{Event, RelayUrl, SubscriptionId};
use nostr::{Event, EventBuilder, RelayUrl, SubscriptionId};

/// Policy Error
#[derive(Debug)]
Expand Down Expand Up @@ -96,3 +96,24 @@ pub trait AdmitPolicy: fmt::Debug + Send + Sync {
Box::pin(async move { Ok(AdmitStatus::Success) })
}
}

/// Authentication middleware
///
/// <https://github.com/nostr-protocol/nips/blob/master/42.md>
pub trait AuthenticationMiddleware: fmt::Debug + Send + Sync {
/// Check if the middleware is ready for authentication
///
/// If the middleware doesn't have a signer yet, this will return `false`.
fn is_ready(&self) -> BoxedFuture<'_, bool>;

/// Build authentication [`Event`].
///
/// Takes an [`EventBuilder`] and returns the signed [`Event`] for authenticating to the relay.
///
/// <https://github.com/nostr-protocol/nips/blob/master/42.md>
fn authenticate<'a>(
&'a self,
relay_url: &'a RelayUrl,
builder: EventBuilder,
) -> BoxedFuture<'a, Result<Event, PolicyError>>;
}
11 changes: 6 additions & 5 deletions crates/nostr-relay-pool/src/pool/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@

use std::sync::Arc;

use nostr::NostrSigner;
use nostr_database::{MemoryDatabase, NostrDatabase};

use super::options::RelayPoolOptions;
use super::RelayPool;
use crate::monitor::Monitor;
use crate::policy::AdmitPolicy;
use crate::policy::{AdmitPolicy, AuthenticationMiddleware};
use crate::transport::websocket::{DefaultWebsocketTransport, WebSocketTransport};

/// Relay Pool builder
Expand All @@ -22,26 +21,28 @@ pub struct RelayPoolBuilder {
pub websocket_transport: Arc<dyn WebSocketTransport>,
/// Admission policy
pub admit_policy: Option<Arc<dyn AdmitPolicy>>,
/// Authentication middleware
///
/// <https://github.com/nostr-protocol/nips/blob/master/42.md>
pub auth_middleware: Option<Arc<dyn AuthenticationMiddleware>>,
/// Relay monitor
pub monitor: Option<Monitor>,
/// Relay pool options
pub opts: RelayPoolOptions,
// Private stuff
#[doc(hidden)]
pub __database: Arc<dyn NostrDatabase>,
#[doc(hidden)]
pub __signer: Option<Arc<dyn NostrSigner>>,
}

impl Default for RelayPoolBuilder {
fn default() -> Self {
Self {
websocket_transport: Arc::new(DefaultWebsocketTransport),
admit_policy: None,
auth_middleware: None,
monitor: None,
opts: RelayPoolOptions::default(),
__database: Arc::new(MemoryDatabase::default()),
__signer: None,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/nostr-relay-pool/src/pool/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ impl InnerRelayPool {
state: SharedState::new(
builder.__database,
builder.websocket_transport,
builder.__signer,
builder.admit_policy,
builder.auth_middleware,
builder.opts.nip42_auto_authentication,
builder.monitor,
),
Expand Down
3 changes: 3 additions & 0 deletions crates/nostr-relay-pool/src/relay/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ pub enum Error {
},
/// Auth failed
AuthenticationFailed,
/// Authentication middleware not set
AuthMiddlewareNotSet,
/// Premature exit
PrematureExit,
}
Expand Down Expand Up @@ -178,6 +180,7 @@ impl fmt::Display for Error {
current.as_millis()
),
Self::AuthenticationFailed => write!(f, "authentication failed"),
Self::AuthMiddlewareNotSet => write!(f, "authentication middleware not set"),
Self::PrematureExit => write!(f, "premature exit"),
}
}
Expand Down
22 changes: 14 additions & 8 deletions crates/nostr-relay-pool/src/relay/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use super::{
Error, Reconciliation, RelayNotification, RelayStatus, SubscriptionActivity,
SubscriptionAutoClosedReason,
};
use crate::policy::AdmitStatus;
use crate::policy::{AdmitStatus, AuthenticationMiddleware};
use crate::pool::RelayPoolNotification;
use crate::relay::status::AtomicRelayStatus;
use crate::shared::SharedState;
Expand Down Expand Up @@ -988,6 +988,7 @@ impl InnerRelay {
);

// Check if NIP42 auto authentication is enabled
// TODO: check also if middleware is set?
if self.state.is_auto_authentication_enabled() {
// Forward action to ingester
let _ = ingester_tx.send(IngesterCommand::Authenticate {
Expand Down Expand Up @@ -1217,13 +1218,18 @@ impl InnerRelay {
}

async fn auth(&self, challenge: String) -> Result<(), Error> {
// Get signer
let signer = self.state.signer().await?;

// Construct event
let event: Event = EventBuilder::auth(challenge, self.url.clone())
.sign(&signer)
.await?;
// Get middleware
let middleware: &Arc<dyn AuthenticationMiddleware> = self
.state
.auth_middleware
.as_ref()
.ok_or(Error::AuthMiddlewareNotSet)?;

// Construct event builder
let builder: EventBuilder = EventBuilder::auth(challenge, self.url.clone());

// Create the authentication event
let event: Event = middleware.authenticate(&self.url, builder).await?;

// Subscribe to notifications
let mut notifications = self.internal_notification_sender.subscribe();
Expand Down
95 changes: 77 additions & 18 deletions crates/nostr-relay-pool/src/relay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,22 +407,29 @@ impl Relay {

// If auth required, wait for authentication adn resend it
if let Some(MachineReadablePrefix::AuthRequired) = MachineReadablePrefix::parse(&message) {
// Check if NIP42 auth is enabled and signer is set
let has_signer: bool = self.inner.state.has_signer().await;
if self.inner.state.is_auto_authentication_enabled() && has_signer {
// Wait that relay authenticate
self.wait_for_authentication(&mut notifications, WAIT_FOR_AUTHENTICATION_TIMEOUT)
// Check if NIP42 auth is enabled and middleware is set
if let Some(middleware) = &self.inner.state.auth_middleware {
let is_enabled: bool = self.inner.state.is_auto_authentication_enabled();
let is_ready: bool = middleware.is_ready().await;

if is_enabled && is_ready {
// Wait that relay authenticate
self.wait_for_authentication(
&mut notifications,
WAIT_FOR_AUTHENTICATION_TIMEOUT,
)
.await?;

// Try to resend event
let (status, message) = self._send_event(&mut notifications, event).await?;
// Try to resend event
let (status, message) = self._send_event(&mut notifications, event).await?;

// Check status
return if status {
Ok(event.id)
} else {
Err(Error::RelayMessage(message))
};
// Check status
return if status {
Ok(event.id)
} else {
Err(Error::RelayMessage(message))
};
}
}
}

Expand Down Expand Up @@ -746,9 +753,10 @@ mod tests {

use async_utility::time;
use nostr_relay_builder::prelude::*;
use tokio::sync::RwLock;

use super::{Error, *};
use crate::policy::{AdmitPolicy, PolicyError};
use crate::policy::{AdmitPolicy, AuthenticationMiddleware, PolicyError};

#[derive(Debug)]
struct CustomTestPolicy {
Expand All @@ -770,10 +778,55 @@ mod tests {
}
}

#[derive(Debug, Default)]
struct AuthenticationPolicy {
signer: RwLock<Option<Arc<dyn NostrSigner>>>,
}

impl AuthenticationPolicy {
async fn set_signer(&self, signer: Option<Arc<dyn NostrSigner>>) {
let mut s = self.signer.write().await;
*s = signer;
}
}

impl AuthenticationMiddleware for AuthenticationPolicy {
fn is_ready(&self) -> BoxedFuture<'_, bool> {
Box::pin(async move { self.signer.read().await.is_some() })
}

fn authenticate<'a>(
&'a self,
_relay_url: &'a RelayUrl,
builder: EventBuilder,
) -> BoxedFuture<'a, Result<Event, PolicyError>> {
Box::pin(async move {
let signer = self.signer.read().await;

match signer.as_ref() {
Some(signer) => builder.sign(signer).await.map_err(PolicyError::backend),
None => {
return Err(PolicyError::backend(Error::AuthenticationFailed));
}
}
})
}
}

fn new_relay(url: RelayUrl, opts: RelayOptions) -> Relay {
Relay::new(url, SharedState::default(), opts)
}

fn new_relay_with_auth_middleware(
url: RelayUrl,
middleware: Arc<dyn AuthenticationMiddleware>,
opts: RelayOptions,
) -> Relay {
let mut state: SharedState = SharedState::default();
state.auth_middleware = Some(middleware);
Relay::new(url, state, opts)
}

/// Setup public (without NIP42 auth) relay with N events to test event fetching
///
/// **Adds ONLY text notes**
Expand Down Expand Up @@ -1161,7 +1214,10 @@ mod tests {
let mock = LocalRelay::run(builder).await.unwrap();
let url = RelayUrl::parse(&mock.url()).unwrap();

let relay: Relay = new_relay(url, RelayOptions::default());
let middleware = Arc::new(AuthenticationPolicy::default());

let relay: Relay =
new_relay_with_auth_middleware(url, middleware.clone(), RelayOptions::default());

relay.inner.state.automatic_authentication(true);

Expand All @@ -1185,7 +1241,7 @@ mod tests {
}

// Set a signer
relay.inner.state.set_signer(keys.clone()).await;
middleware.set_signer(Some(Arc::new(keys.clone()))).await;

// Send as authenticated
let event = EventBuilder::text_note("Test")
Expand All @@ -1204,7 +1260,10 @@ mod tests {
let mock = LocalRelay::run(builder).await.unwrap();
let url = RelayUrl::parse(&mock.url()).unwrap();

let relay: Relay = new_relay(url, RelayOptions::default());
let middleware = Arc::new(AuthenticationPolicy::default());

let relay: Relay =
new_relay_with_auth_middleware(url, middleware.clone(), RelayOptions::default());

relay.connect();

Expand Down Expand Up @@ -1256,7 +1315,7 @@ mod tests {
assert!(matches!(err, Error::AuthenticationFailed));

// Set a signer
relay.inner.state.set_signer(keys).await;
middleware.set_signer(Some(Arc::new(keys))).await;

// Authenticated fetch
let res = relay
Expand Down
Loading