auth.rs (5077B)
1 #![forbid(unsafe_code)] 2 3 use jsonrpsee::core::server::Extensions; 4 5 use crate::core::transport_publish::{PublishPrincipal, TransportPublishStore, hash_bearer_token}; 6 7 use super::RpcError; 8 9 #[cfg(test)] 10 pub(crate) const TRANSPORT_PUBLISH_AUTH_MODE: &str = "scoped_bearer_token"; 11 12 #[derive(Clone, Debug, PartialEq, Eq)] 13 pub(crate) enum TransportPublishAuthorization { 14 Authorized(PublishPrincipal), 15 Missing, 16 Invalid, 17 } 18 19 pub(crate) fn authorize_transport_publish_request( 20 authorization_header: Option<&str>, 21 store: &TransportPublishStore, 22 ) -> TransportPublishAuthorization { 23 let Some(authorization_header) = authorization_header else { 24 return TransportPublishAuthorization::Missing; 25 }; 26 27 let mut parts = authorization_header.split_whitespace(); 28 let scheme = parts.next().unwrap_or_default(); 29 let token = parts.next().unwrap_or_default(); 30 31 if !scheme.eq_ignore_ascii_case("bearer") || token.is_empty() || parts.next().is_some() { 32 return TransportPublishAuthorization::Invalid; 33 } 34 35 match store.principal_for_token_hash(hash_bearer_token(token).as_str()) { 36 Ok(Some(principal)) => TransportPublishAuthorization::Authorized(principal), 37 Ok(None) | Err(_) => TransportPublishAuthorization::Invalid, 38 } 39 } 40 41 pub(crate) fn require_publish_principal( 42 extensions: &Extensions, 43 ) -> Result<PublishPrincipal, RpcError> { 44 match extensions 45 .get::<TransportPublishAuthorization>() 46 .cloned() 47 .unwrap_or(TransportPublishAuthorization::Missing) 48 { 49 TransportPublishAuthorization::Authorized(principal) => Ok(principal), 50 TransportPublishAuthorization::Missing => Err(RpcError::Unauthorized( 51 "transport publish bearer token required".to_string(), 52 )), 53 TransportPublishAuthorization::Invalid => Err(RpcError::Unauthorized( 54 "invalid transport publish bearer token".to_string(), 55 )), 56 } 57 } 58 59 #[cfg(test)] 60 mod tests { 61 use jsonrpsee::core::server::Extensions; 62 use radroots_protocol::radrootsd::transport_publish::v5::{ 63 NostrTargetSourcePolicy, TargetPolicyName, 64 }; 65 66 use super::{ 67 TRANSPORT_PUBLISH_AUTH_MODE, TransportPublishAuthorization, 68 authorize_transport_publish_request, require_publish_principal, 69 }; 70 use crate::core::transport_publish::{ 71 PublishJobVisibility, PublishPrincipalInit, TransportPublishStore, generate_bearer_token, 72 hash_bearer_token, 73 }; 74 75 fn store_with_token() -> (TransportPublishStore, String) { 76 let store = TransportPublishStore::memory().expect("store"); 77 let token = generate_bearer_token(); 78 store 79 .create_principal(PublishPrincipalInit { 80 label: "tester".to_owned(), 81 token_hash: hash_bearer_token(token.as_str()), 82 allowed_pubkeys: vec!["a".repeat(64)], 83 allowed_kinds: vec![30_402], 84 allowed_target_policies: vec![TargetPolicyName::Nostr], 85 allowed_explicit_transport_kinds: Vec::new(), 86 allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly], 87 allow_request_targets: false, 88 job_visibility: PublishJobVisibility::Own, 89 expires_at_unix: None, 90 }) 91 .expect("principal"); 92 (store, token) 93 } 94 95 #[test] 96 fn transport_publish_auth_accepts_matching_bearer_token() { 97 let (store, token) = store_with_token(); 98 let header = format!("Bearer {token}"); 99 let auth = authorize_transport_publish_request(Some(header.as_str()), &store); 100 assert!(matches!(auth, TransportPublishAuthorization::Authorized(_))); 101 assert_eq!(TRANSPORT_PUBLISH_AUTH_MODE, "scoped_bearer_token"); 102 } 103 104 #[test] 105 fn transport_publish_auth_rejects_missing_and_invalid_headers() { 106 let (store, _token) = store_with_token(); 107 assert_eq!( 108 authorize_transport_publish_request(None, &store), 109 TransportPublishAuthorization::Missing 110 ); 111 assert_eq!( 112 authorize_transport_publish_request(Some("Basic secret"), &store), 113 TransportPublishAuthorization::Invalid 114 ); 115 assert_eq!( 116 authorize_transport_publish_request(Some("Bearer wrong"), &store), 117 TransportPublishAuthorization::Invalid 118 ); 119 } 120 121 #[test] 122 fn require_publish_principal_reads_authorized_extensions() { 123 let (store, token) = store_with_token(); 124 let header = format!("Bearer {token}"); 125 let auth = authorize_transport_publish_request(Some(header.as_str()), &store); 126 let mut extensions = Extensions::new(); 127 extensions.insert(auth); 128 require_publish_principal(&extensions).expect("authorized"); 129 } 130 131 #[test] 132 fn require_publish_principal_rejects_missing_extensions() { 133 let err = 134 require_publish_principal(&Extensions::new()).expect_err("missing auth should fail"); 135 assert!(err.to_string().contains("required")); 136 } 137 }