lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit ca63321d970efc08c7a60066862aec1d4beb0a78
parent e8c3c6c886ce94a8b989b099396b65e57da82e07
Author: triesap <tyson@radroots.org>
Date:   Wed, 24 Jun 2026 06:25:46 +0000

nostr_connect: add typed permission matching

- add request-level permission matching helpers
- normalize sign_event numeric and kind-prefixed parameters
- reject nonnumeric sign_event parameters during matching
- cover exact method and sign_event kind readiness cases

Diffstat:
Mcrates/nostr_connect/src/permission.rs | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/nostr_connect/tests/coverage.rs | 47+++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 104 insertions(+), 0 deletions(-)

diff --git a/crates/nostr_connect/src/permission.rs b/crates/nostr_connect/src/permission.rs @@ -27,6 +27,39 @@ impl RadrootsNostrConnectPermission { parameter: Some(parameter.into()), } } + + pub fn matches_request( + &self, + method: &RadrootsNostrConnectMethod, + parameter: Option<&str>, + ) -> bool { + if self.method != *method { + return false; + } + match (&self.method, self.parameter.as_deref(), parameter) { + (RadrootsNostrConnectMethod::SignEvent, None, _) => true, + (RadrootsNostrConnectMethod::SignEvent, Some(configured), Some(requested)) => { + match ( + sign_event_kind_parameter(configured), + sign_event_kind_parameter(requested), + ) { + (Some(configured), Some(requested)) => configured == requested, + _ => false, + } + } + (_, None, None) => true, + (_, Some(configured), Some(requested)) => configured == requested, + _ => false, + } + } + + pub fn matches_sign_event_kind(&self, event_kind: u32) -> bool { + let event_kind = event_kind.to_string(); + self.matches_request( + &RadrootsNostrConnectMethod::SignEvent, + Some(event_kind.as_str()), + ) + } } impl fmt::Display for RadrootsNostrConnectPermission { @@ -85,6 +118,30 @@ impl RadrootsNostrConnectPermissions { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + pub fn allows_request( + &self, + method: &RadrootsNostrConnectMethod, + parameter: Option<&str>, + ) -> bool { + self.0 + .iter() + .any(|permission| permission.matches_request(method, parameter)) + } + + pub fn allows_sign_event_kind(&self, event_kind: u32) -> bool { + self.0 + .iter() + .any(|permission| permission.matches_sign_event_kind(event_kind)) + } +} + +fn sign_event_kind_parameter(value: &str) -> Option<u32> { + let value = value.strip_prefix("kind:").unwrap_or(value); + if value.is_empty() || !value.chars().all(|character| character.is_ascii_digit()) { + return None; + } + value.parse().ok() } impl From<Vec<RadrootsNostrConnectPermission>> for RadrootsNostrConnectPermissions { diff --git a/crates/nostr_connect/tests/coverage.rs b/crates/nostr_connect/tests/coverage.rs @@ -169,6 +169,53 @@ fn error_method_and_permission_surfaces_cover_public_paths() { RadrootsNostrConnectPermissions::from_str("sign_event:,ping"), Err(RadrootsNostrConnectError::InvalidPermission(value)) if value == "sign_event:" )); + + let all_sign_events = + RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::SignEvent); + assert!(all_sign_events.matches_sign_event_kind(30402)); + assert!(all_sign_events.matches_request(&RadrootsNostrConnectMethod::SignEvent, None)); + assert!(!all_sign_events.matches_request(&RadrootsNostrConnectMethod::Ping, None)); + + let numeric_sign_event = RadrootsNostrConnectPermission::with_parameter( + RadrootsNostrConnectMethod::SignEvent, + "30402", + ); + let kind_prefixed_sign_event = RadrootsNostrConnectPermission::with_parameter( + RadrootsNostrConnectMethod::SignEvent, + "kind:30402", + ); + assert!(numeric_sign_event.matches_sign_event_kind(30402)); + assert!(kind_prefixed_sign_event.matches_sign_event_kind(30402)); + assert!( + numeric_sign_event + .matches_request(&RadrootsNostrConnectMethod::SignEvent, Some("kind:30402")) + ); + assert!(!numeric_sign_event.matches_sign_event_kind(3040)); + assert!( + !RadrootsNostrConnectPermission::with_parameter( + RadrootsNostrConnectMethod::SignEvent, + "130402" + ) + .matches_sign_event_kind(30402) + ); + assert!( + !RadrootsNostrConnectPermission::with_parameter( + RadrootsNostrConnectMethod::SignEvent, + "not-a-kind" + ) + .matches_request( + &RadrootsNostrConnectMethod::SignEvent, + Some("also-not-a-kind") + ) + ); + + let typed_permissions = RadrootsNostrConnectPermissions::from(vec![ + RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Ping), + kind_prefixed_sign_event, + ]); + assert!(typed_permissions.allows_request(&RadrootsNostrConnectMethod::Ping, None)); + assert!(typed_permissions.allows_sign_event_kind(30402)); + assert!(!typed_permissions.allows_sign_event_kind(0)); } #[test]