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:
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]