radrootsd

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

mod.rs (6518B)


      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 transport_publish;
     10 
     11 pub fn register_all(
     12     root: &mut RpcModule<RpcContext>,
     13     ctx: RpcContext,
     14     registry: MethodRegistry,
     15 ) -> Result<()> {
     16     if ctx.state.transport_publish.config.enabled {
     17         root.merge(transport_publish::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 crate::app::identity_storage::DaemonIdentity;
     28     use jsonrpsee::server::RpcModule;
     29     use radroots_protocol::radrootsd::transport_publish::v5::RETICULUM_UNAVAILABLE_MESSAGE as RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE;
     30 
     31     use super::register_all;
     32     use crate::app::config::{Nip46Config, TransportPublishConfig};
     33     use crate::core::Radrootsd;
     34     use crate::transport::jsonrpc::auth::TransportPublishAuthorization;
     35     use crate::transport::jsonrpc::{MethodRegistry, RpcContext};
     36 
     37     mod removed_surface_fixtures {
     38         pub const BRIDGE_STATUS_METHOD: &str = "bridge.status";
     39     }
     40 
     41     fn state(transport_publish_enabled: bool, nip46_public_jsonrpc_enabled: bool) -> Radrootsd {
     42         let identity = DaemonIdentity::generate();
     43         let transport_publish = TransportPublishConfig {
     44             enabled: transport_publish_enabled,
     45             ..TransportPublishConfig::default()
     46         };
     47         let nip46 = Nip46Config {
     48             public_jsonrpc_enabled: nip46_public_jsonrpc_enabled,
     49             ..Nip46Config::default()
     50         };
     51         Radrootsd::new(identity, transport_publish, nip46).expect("state")
     52     }
     53 
     54     #[test]
     55     fn register_all_exposes_transport_publish_methods_by_default() {
     56         let registry = MethodRegistry::default();
     57         let ctx = RpcContext::new(state(true, false));
     58         let mut root = RpcModule::new(ctx.clone());
     59         register_all(&mut root, ctx, registry).expect("register");
     60 
     61         assert!(root.method("transport.publish.capabilities").is_some());
     62         assert!(root.method("transport.publish.event").is_some());
     63         assert!(root.method("transport.publish.job.get").is_some());
     64         assert!(root.method("transport.publish.job.list").is_some());
     65         assert!(
     66             root.method(removed_surface_fixtures::BRIDGE_STATUS_METHOD)
     67                 .is_none()
     68         );
     69         assert!(root.method("nip46.connect").is_none());
     70     }
     71 
     72     #[test]
     73     fn register_all_exposes_nip46_when_public_jsonrpc_is_enabled() {
     74         let registry = MethodRegistry::default();
     75         let ctx = RpcContext::new(state(true, true));
     76         let mut root = RpcModule::new(ctx.clone());
     77         register_all(&mut root, ctx, registry).expect("register");
     78 
     79         assert!(root.method("transport.publish.capabilities").is_some());
     80         assert!(root.method("nip46.connect").is_some());
     81     }
     82 
     83     #[tokio::test]
     84     async fn publish_capabilities_rejects_unauthenticated_requests() {
     85         let registry = MethodRegistry::default();
     86         let ctx = RpcContext::new(state(true, false));
     87         let mut root = RpcModule::new(ctx.clone());
     88         register_all(&mut root, ctx, registry).expect("register");
     89 
     90         let (response, _stream) = root
     91             .raw_json_request(
     92                 r#"{"jsonrpc":"2.0","method":"transport.publish.capabilities","id":1}"#,
     93                 1,
     94             )
     95             .await
     96             .expect("request");
     97         assert!(response.get().contains("unauthorized"));
     98     }
     99 
    100     #[tokio::test]
    101     async fn publish_capabilities_accepts_authenticated_requests() {
    102         let registry = MethodRegistry::default();
    103         let ctx = RpcContext::new(state(true, false));
    104         let principal = ctx
    105             .state
    106             .transport_publish
    107             .store
    108             .create_principal(crate::core::transport_publish::PublishPrincipalInit {
    109                 label: "tester".to_owned(),
    110                 token_hash: crate::core::transport_publish::hash_bearer_token("secret"),
    111                 allowed_pubkeys: vec!["a".repeat(64)],
    112                 allowed_kinds: vec![30_402],
    113                 allowed_target_policies: vec![
    114                     radroots_protocol::radrootsd::transport_publish::v5::TargetPolicyName::Nostr,
    115                 ],
    116                 allowed_explicit_transport_kinds: Vec::new(),
    117                 allowed_nostr_source_policies: vec![
    118                     radroots_protocol::radrootsd::transport_publish::v5::NostrTargetSourcePolicy::DaemonDefaultOnly,
    119                 ],
    120                 allow_request_targets: false,
    121                 job_visibility: crate::core::transport_publish::PublishJobVisibility::Own,
    122                 expires_at_unix: None,
    123             })
    124             .expect("principal");
    125         let mut root = RpcModule::new(ctx.clone());
    126         root.extensions_mut()
    127             .insert(TransportPublishAuthorization::Authorized(principal));
    128         register_all(&mut root, ctx, registry).expect("register");
    129 
    130         let (response, _stream) = root
    131             .raw_json_request(
    132                 r#"{"jsonrpc":"2.0","method":"transport.publish.capabilities","id":1}"#,
    133                 1,
    134             )
    135             .await
    136             .expect("request");
    137         let response: serde_json::Value =
    138             serde_json::from_str(response.get()).expect("capabilities response JSON");
    139         let result = &response["result"];
    140         assert_eq!(result["api_version"], "radrootsd.transport_publish.v5");
    141         assert_eq!(result["auth"]["mode"], "scoped_bearer_token");
    142         assert_eq!(result["publish"]["raw_event_json_ingress"], true);
    143         let reticulum = result["publish"]["transports"]
    144             .as_array()
    145             .expect("transport capabilities")
    146             .iter()
    147             .find(|transport| transport["transport"] == "reticulum")
    148             .expect("Reticulum capability");
    149         assert_eq!(reticulum["configured"], true);
    150         assert_eq!(reticulum["implementation"], "real");
    151         assert_eq!(reticulum["usable_for_delivery"], false);
    152         assert_eq!(
    153             reticulum["capabilities"],
    154             serde_json::json!({
    155                 "deliver": false,
    156                 "fetch": false,
    157                 "discovery": false,
    158                 "gateway_forwarding": false,
    159                 "receipt_observation": false,
    160             })
    161         );
    162         assert_eq!(reticulum["message"], RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE);
    163     }
    164 }