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 }