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