radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

mod.rs (5143B)


      1 #![forbid(unsafe_code)]
      2 
      3 use anyhow::Result;
      4 use jsonrpsee::server::RpcModule;
      5 
      6 use crate::transport::jsonrpc::{MethodRegistry, RpcContext};
      7 
      8 pub mod nip46;
      9 pub mod publish_proxy;
     10 
     11 pub fn register_all(
     12     root: &mut RpcModule<RpcContext>,
     13     ctx: RpcContext,
     14     registry: MethodRegistry,
     15 ) -> Result<()> {
     16     if ctx.state.publish_proxy.config.enabled {
     17         root.merge(publish_proxy::module(ctx.clone(), registry.clone())?)?;
     18     }
     19     if ctx.state.nip46_config.public_jsonrpc_enabled {
     20         root.merge(nip46::module(ctx, registry)?)?;
     21     }
     22     Ok(())
     23 }
     24 
     25 #[cfg(test)]
     26 mod tests {
     27     use jsonrpsee::server::RpcModule;
     28     use radroots_identity::RadrootsIdentity;
     29     use radroots_nostr::prelude::RadrootsNostrMetadata;
     30 
     31     use super::register_all;
     32     use crate::app::config::{Nip46Config, PublishProxyConfig};
     33     use crate::core::Radrootsd;
     34     use crate::transport::jsonrpc::auth::PublishProxyAuthorization;
     35     use crate::transport::jsonrpc::{MethodRegistry, RpcContext};
     36 
     37     fn state(publish_proxy_enabled: bool, nip46_public_jsonrpc_enabled: bool) -> Radrootsd {
     38         let identity = RadrootsIdentity::generate();
     39         let metadata: RadrootsNostrMetadata =
     40             serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata");
     41         let publish_proxy = PublishProxyConfig {
     42             enabled: publish_proxy_enabled,
     43             ..PublishProxyConfig::default()
     44         };
     45         let nip46 = Nip46Config {
     46             public_jsonrpc_enabled: nip46_public_jsonrpc_enabled,
     47             ..Nip46Config::default()
     48         };
     49         Radrootsd::new(identity, metadata, publish_proxy, nip46).expect("state")
     50     }
     51 
     52     #[test]
     53     fn register_all_exposes_publish_proxy_methods_by_default() {
     54         let registry = MethodRegistry::default();
     55         let ctx = RpcContext::new(state(true, false), registry.clone());
     56         let mut root = RpcModule::new(ctx.clone());
     57         register_all(&mut root, ctx, registry).expect("register");
     58 
     59         assert!(root.method("publish.capabilities").is_some());
     60         assert!(root.method("publish.event").is_some());
     61         assert!(root.method("publish.job.get").is_some());
     62         assert!(root.method("publish.job.list").is_some());
     63         assert!(root.method("publish.relays.resolve").is_some());
     64         let legacy_method = ["br", "idge.status"].concat();
     65         assert!(root.method(legacy_method.as_str()).is_none());
     66         assert!(root.method("nip46.connect").is_none());
     67     }
     68 
     69     #[test]
     70     fn register_all_exposes_nip46_when_public_jsonrpc_is_enabled() {
     71         let registry = MethodRegistry::default();
     72         let ctx = RpcContext::new(state(true, true), registry.clone());
     73         let mut root = RpcModule::new(ctx.clone());
     74         register_all(&mut root, ctx, registry).expect("register");
     75 
     76         assert!(root.method("publish.capabilities").is_some());
     77         assert!(root.method("nip46.connect").is_some());
     78     }
     79 
     80     #[tokio::test]
     81     async fn publish_capabilities_rejects_unauthenticated_requests() {
     82         let registry = MethodRegistry::default();
     83         let ctx = RpcContext::new(state(true, false), registry.clone());
     84         let mut root = RpcModule::new(ctx.clone());
     85         register_all(&mut root, ctx, registry).expect("register");
     86 
     87         let (response, _stream) = root
     88             .raw_json_request(
     89                 r#"{"jsonrpc":"2.0","method":"publish.capabilities","id":1}"#,
     90                 1,
     91             )
     92             .await
     93             .expect("request");
     94         assert!(response.get().contains("unauthorized"));
     95     }
     96 
     97     #[tokio::test]
     98     async fn publish_capabilities_accepts_authenticated_requests() {
     99         let registry = MethodRegistry::default();
    100         let ctx = RpcContext::new(state(true, false), registry.clone());
    101         let principal = ctx
    102             .state
    103             .publish_proxy
    104             .store
    105             .create_principal(crate::core::publish_proxy::PublishPrincipalInit {
    106                 label: "tester".to_owned(),
    107                 token_hash: crate::core::publish_proxy::hash_bearer_token("secret"),
    108                 allowed_pubkeys: vec!["a".repeat(64)],
    109                 allowed_kinds: vec![30_402],
    110                 allowed_relay_policies: vec![
    111                     radroots_publish_proxy_protocol::PublishRelayPolicy::DaemonDefaultOnly,
    112                 ],
    113                 allow_request_relays: false,
    114                 job_visibility: crate::core::publish_proxy::PublishJobVisibility::Own,
    115                 expires_at_unix: None,
    116             })
    117             .expect("principal");
    118         let mut root = RpcModule::new(ctx.clone());
    119         root.extensions_mut()
    120             .insert(PublishProxyAuthorization::Authorized(principal));
    121         register_all(&mut root, ctx, registry).expect("register");
    122 
    123         let (response, _stream) = root
    124             .raw_json_request(
    125                 r#"{"jsonrpc":"2.0","method":"publish.capabilities","id":1}"#,
    126                 1,
    127             )
    128             .await
    129             .expect("request");
    130         assert!(response.get().contains("\"scoped_bearer_token\""));
    131         assert!(response.get().contains("\"signed_event_ingress\":true"));
    132     }
    133 }