myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit ef668a1744c2a08fcb48fcb597d81af36febca9f
parent 8418646d0a621227d5528c234ba121bec179f31e
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 20:03:22 +0000

myc: validate packaged nip46 interoperability

- exercise the canonical final client against the Myc server through a mocked relay
- remove every hidden NIP-46 migration-prelude and temporary alias reference
- retain external-client auth, permission, signing, crypto, restart, and revocation coverage
- add request-specific timeout diagnostics for package interoperability failures

Diffstat:
Msrc/app/backend.rs | 17+++++++----------
Msrc/cli.rs | 20+++++++-------------
Msrc/config.rs | 16+++++++---------
Msrc/control.rs | 21++++++++-------------
Msrc/discovery.rs | 4++--
Msrc/error.rs | 4++--
Msrc/operability/mod.rs | 2+-
Msrc/policy.rs | 187++++++++++++++++++++++++++-----------------------------------------------------
Msrc/transport/nip46.rs | 383++++++++++++++++++++++++++++++++-----------------------------------------------
Mtests/discovery_cli.rs | 4++--
Mtests/nip46_e2e.rs | 329+++++++++++++++++++++++++++++++++++++++++++------------------------------------
11 files changed, 432 insertions(+), 555 deletions(-)

diff --git a/src/app/backend.rs b/src/app/backend.rs @@ -13,10 +13,7 @@ use crate::signer::prelude::{ RadrootsNostrSignerSignOutput, RadrootsNostrSignerWorkflowId, }; use nostr::{PublicKey, RelayUrl, UnsignedEvent}; -use radroots_nostr_connect::prelude::{ - RadrootsNostrConnectMethod, RadrootsNostrConnectPermissions, RadrootsNostrConnectRequest, - RadrootsNostrConnectRequestMessage, -}; +use radroots_nostr_connect::{Method, Request, message::RequestMessage, permission::Permissions}; use crate::app::MycSignerContext; use crate::error::MycError; @@ -143,7 +140,7 @@ impl RadrootsNostrSignerBackend for MycSignerBackend { fn evaluate_connect_request( &self, client_public_key: PublicKey, - request: RadrootsNostrConnectRequest, + request: Request, ) -> Result<RadrootsNostrSignerConnectEvaluation, RadrootsNostrSignerError> { self.manager()? .evaluate_connect_request(client_public_key, request) @@ -159,7 +156,7 @@ impl RadrootsNostrSignerBackend for MycSignerBackend { fn set_granted_permissions( &self, connection_id: &RadrootsNostrSignerConnectionId, - granted_permissions: RadrootsNostrConnectPermissions, + granted_permissions: Permissions, ) -> Result<RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerError> { self.manager()? .set_granted_permissions(connection_id, granted_permissions) @@ -168,7 +165,7 @@ impl RadrootsNostrSignerBackend for MycSignerBackend { fn approve_connection( &self, connection_id: &RadrootsNostrSignerConnectionId, - granted_permissions: RadrootsNostrConnectPermissions, + granted_permissions: Permissions, ) -> Result<RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerError> { self.manager()? .approve_connection(connection_id, granted_permissions) @@ -210,7 +207,7 @@ impl RadrootsNostrSignerBackend for MycSignerBackend { fn set_pending_request( &self, connection_id: &RadrootsNostrSignerConnectionId, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, ) -> Result<RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerError> { self.manager()? .set_pending_request(connection_id, request_message) @@ -296,7 +293,7 @@ impl RadrootsNostrSignerBackend for MycSignerBackend { fn evaluate_request( &self, connection_id: &RadrootsNostrSignerConnectionId, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, ) -> Result<RadrootsNostrSignerRequestEvaluation, RadrootsNostrSignerError> { self.manager()? .evaluate_request(connection_id, request_message) @@ -314,7 +311,7 @@ impl RadrootsNostrSignerBackend for MycSignerBackend { &self, connection_id: &RadrootsNostrSignerConnectionId, request_id: &str, - method: RadrootsNostrConnectMethod, + method: Method, decision: RadrootsNostrSignerRequestDecision, message: Option<String>, ) -> Result<RadrootsNostrSignerRequestAuditRecord, RadrootsNostrSignerError> { diff --git a/src/cli.rs b/src/cli.rs @@ -7,7 +7,7 @@ use crate::signer::prelude::{ RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerRequestAuditRecord, }; use clap::{Args, Parser, Subcommand, ValueEnum}; -use radroots_nostr_connect::prelude::RadrootsNostrConnectPermissions; +use radroots_nostr_connect::permission::Permissions; use serde::Serialize; use zeroize::Zeroizing; @@ -716,7 +716,7 @@ fn granted_permissions_for_approval( connections: &[RadrootsNostrSignerConnectionRecord], connection_id: &RadrootsNostrSignerConnectionId, grants: &[String], -) -> Result<RadrootsNostrConnectPermissions, MycError> { +) -> Result<Permissions, MycError> { if !grants.is_empty() { return policy.validate_operator_grants(parse_permission_values(grants)?); } @@ -1055,7 +1055,7 @@ mod tests { use crate::signer::prelude::RadrootsNostrSignerConnectionDraft; use clap::Parser; use nostr::Timestamp; - use radroots_nostr_connect::prelude::RadrootsNostrConnectRequest; + use radroots_nostr_connect::Request; use serde_json::json; use crate::audit::{MycOperationAuditKind, MycOperationAuditOutcome, MycOperationAuditRecord}; @@ -1144,10 +1144,7 @@ mod tests { let request_evaluation = manager .evaluate_request( &connection.connection_id, - radroots_nostr_connect::prelude::RadrootsNostrConnectRequestMessage::new( - "request-1", - RadrootsNostrConnectRequest::Ping, - ), + radroots_nostr_connect::message::RequestMessage::new("request-1", Request::Ping), ) .expect("record audit"); runtime.record_operation_audit(&MycOperationAuditRecord::new( @@ -1192,9 +1189,9 @@ mod tests { let denied = manager .evaluate_request( &connection.connection_id, - radroots_nostr_connect::prelude::RadrootsNostrConnectRequestMessage::new( + radroots_nostr_connect::message::RequestMessage::new( "request-1", - RadrootsNostrConnectRequest::SignEvent( + Request::SignEvent( radroots_nostr_connect::message::UnsignedEvent::from_json( &json!({ "pubkey": runtime.user_identity().public_key().to_hex(), @@ -1216,10 +1213,7 @@ mod tests { let challenged_eval = manager .evaluate_request( &challenged.connection_id, - radroots_nostr_connect::prelude::RadrootsNostrConnectRequestMessage::new( - "request-2", - RadrootsNostrConnectRequest::Ping, - ), + radroots_nostr_connect::message::RequestMessage::new("request-2", Request::Ping), ) .expect("challenged request"); diff --git a/src/config.rs b/src/config.rs @@ -7,7 +7,7 @@ use crate::nostr_contract::RadrootsNostrRelayUrl; use crate::paths::{RadrootsPathResolver, RadrootsRuntimePathPolicyContract}; use crate::signer::prelude::RadrootsNostrSignerApprovalRequirement; use nostr::PublicKey; -use radroots_nostr_connect::prelude::RadrootsNostrConnectPermissions; +use radroots_nostr_connect::permission::Permissions; use serde::{Deserialize, Serialize}; use tracing_subscriber::EnvFilter; @@ -188,7 +188,7 @@ pub struct MycPolicyConfig { pub connection_approval: MycConnectionApproval, pub trusted_client_pubkeys: Vec<String>, pub denied_client_pubkeys: Vec<String>, - pub permission_ceiling: RadrootsNostrConnectPermissions, + pub permission_ceiling: Permissions, pub allowed_sign_event_kinds: Vec<u16>, pub auth_url: Option<String>, pub auth_pending_ttl_secs: u64, @@ -308,7 +308,7 @@ impl Default for MycPolicyConfig { connection_approval: MycConnectionApproval::ExplicitUser, trusted_client_pubkeys: Vec::new(), denied_client_pubkeys: Vec::new(), - permission_ceiling: RadrootsNostrConnectPermissions::default(), + permission_ceiling: Permissions::default(), allowed_sign_event_kinds: Vec::new(), auth_url: None, auth_pending_ttl_secs: 900, @@ -1553,12 +1553,10 @@ fn parse_permissions_env( value: &str, path: &Path, line_number: usize, -) -> Result<RadrootsNostrConnectPermissions, MycError> { - value - .parse::<RadrootsNostrConnectPermissions>() - .map_err(|error| { - config_parse_error(path, line_number, format!("{key} parse error: {error}")) - }) +) -> Result<Permissions, MycError> { + value.parse::<Permissions>().map_err(|error| { + config_parse_error(path, line_number, format!("{key} parse error: {error}")) + }) } fn parse_u16_list_env( diff --git a/src/control.rs b/src/control.rs @@ -6,10 +6,7 @@ use crate::signer::prelude::{ RadrootsNostrSignerPublishTransition, RadrootsNostrSignerPublishWorkflowRecord, RadrootsNostrSignerRequestId, RadrootsNostrSignerWorkflowId, }; -use radroots_nostr_connect::prelude::{ - RadrootsNostrConnectPermission, RadrootsNostrConnectPermissions, RadrootsNostrConnectRequest, - RadrootsNostrConnectResponse, RadrootsNostrConnectUri, -}; +use radroots_nostr_connect::{Permission, Request, Response, permission::Permissions, uri::Uri}; use serde::Serialize; use crate::app::MycRuntime; @@ -78,9 +75,9 @@ pub async fn accept_client_uri( )); } - let client_uri = match RadrootsNostrConnectUri::parse(uri)? { - RadrootsNostrConnectUri::Client(client_uri) => client_uri, - RadrootsNostrConnectUri::Bunker(_) => { + let client_uri = match Uri::parse(uri)? { + Uri::Client(client_uri) => client_uri, + Uri::Bunker(_) => { return Err(MycError::InvalidOperation( "connect accept requires a nostrconnect:// client URI".to_owned(), )); @@ -91,7 +88,7 @@ pub async fn accept_client_uri( MycError::InvalidOperation("NIP-46 client public key conversion failed".to_owned()) })?; - let request = RadrootsNostrConnectRequest::Connect { + let request = Request::Connect { remote_signer_public_key: runtime.signer_identity().public_identity().public_key(), secret: Some(client_uri.secret().to_owned()), requested_permissions: client_uri.metadata().requested_permissions().clone(), @@ -160,7 +157,7 @@ pub async fn accept_client_uri( let event = handler.build_response_event( client_public_key, response_request_id.clone(), - RadrootsNostrConnectResponse::ConnectSecretEcho(client_uri.secret().to_owned()), + Response::ConnectSecretEcho(client_uri.secret().to_owned()), )?; let client_relays = client_uri .relays() @@ -310,9 +307,7 @@ pub async fn accept_client_uri( }) } -pub fn parse_permission_values( - values: &[String], -) -> Result<RadrootsNostrConnectPermissions, MycError> { +pub fn parse_permission_values(values: &[String]) -> Result<Permissions, MycError> { let mut permissions = Vec::new(); for value in values { for fragment in value.split(',') { @@ -320,7 +315,7 @@ pub fn parse_permission_values( if trimmed.is_empty() { continue; } - permissions.push(RadrootsNostrConnectPermission::from_str(trimmed)?); + permissions.push(Permission::from_str(trimmed)?); } } permissions.sort(); diff --git a/src/discovery.rs b/src/discovery.rs @@ -10,7 +10,7 @@ use crate::nostr_contract::{ radroots_nostr_metadata_has_fields, radroots_nostr_tag_first_value, }; use crate::signer::prelude::RadrootsNostrSignerRequestId; -use radroots_nostr_connect::prelude::RadrootsNostrConnectUri; +use radroots_nostr_connect::uri::Uri; use serde::{Deserialize, Serialize}; use tokio::task::JoinSet; @@ -2102,7 +2102,7 @@ fn render_nostrconnect_url( serializer.append_pair("relay", relay.as_str()); } let bunker_uri = format!("bunker://{signer_public_key}?{}", serializer.finish()); - let bunker_uri = RadrootsNostrConnectUri::parse(&bunker_uri)?.to_string(); + let bunker_uri = Uri::parse(&bunker_uri)?.to_string(); let encoded_bunker_uri: String = url::form_urlencoded::byte_serialize(bunker_uri.as_bytes()).collect(); let rendered = template.replace("<nostrconnect>", &encoded_bunker_uri); diff --git a/src/error.rs b/src/error.rs @@ -6,7 +6,7 @@ use crate::host_identity::IdentityError; use crate::nostr_contract::RadrootsNostrError; use crate::signer::prelude::RadrootsNostrSignerError; use crate::sql::error::SqlError; -use radroots_nostr_connect::prelude::RadrootsNostrConnectError; +use radroots_nostr_connect::Error as NostrConnectError; use thiserror::Error; use crate::config::MycTransportDeliveryPolicy; @@ -280,7 +280,7 @@ pub enum MycError { #[error(transparent)] Nostr(#[from] RadrootsNostrError), #[error(transparent)] - NostrConnect(#[from] RadrootsNostrConnectError), + NostrConnect(#[from] NostrConnectError), #[error(transparent)] SignerState(#[from] RadrootsNostrSignerError), #[error(transparent)] diff --git a/src/operability/mod.rs b/src/operability/mod.rs @@ -1788,7 +1788,7 @@ mod tests { .record_request( &connection.connection_id, "req-live-metrics", - radroots_nostr_connect::prelude::RadrootsNostrConnectMethod::Ping, + radroots_nostr_connect::Method::Ping, RadrootsNostrSignerRequestDecision::Allowed, None, ) diff --git a/src/policy.rs b/src/policy.rs @@ -8,9 +8,8 @@ use crate::signer::prelude::{ RadrootsNostrSignerNip46ConnectDecision, RadrootsNostrSignerNip46Policy, }; use nostr::PublicKey; -use radroots_nostr_connect::prelude::{ - RadrootsNostrConnectMethod, RadrootsNostrConnectPermission, RadrootsNostrConnectPermissions, - RadrootsNostrConnectRequest, RadrootsNostrConnectRequestMessage, +use radroots_nostr_connect::{ + Method, Permission, Request, message::RequestMessage, permission::Permissions, }; use crate::config::{MycConnectionApproval, MycPolicyConfig}; @@ -28,7 +27,7 @@ pub struct MycPolicyContext { default_connect_decision: MycConnectDecision, trusted_client_pubkeys: BTreeSet<String>, denied_client_pubkeys: BTreeSet<String>, - permission_ceiling: RadrootsNostrConnectPermissions, + permission_ceiling: Permissions, allowed_sign_event_kinds: BTreeSet<u16>, auth_url: Option<String>, auth_pending_ttl_secs: u64, @@ -116,29 +115,24 @@ impl MycPolicyContext { }) } - pub fn auto_granted_permissions( - &self, - requested_permissions: &RadrootsNostrConnectPermissions, - ) -> RadrootsNostrConnectPermissions { + pub fn auto_granted_permissions(&self, requested_permissions: &Permissions) -> Permissions { self.filtered_requested_permissions(requested_permissions) } pub fn filtered_requested_permissions( &self, - requested_permissions: &RadrootsNostrConnectPermissions, - ) -> RadrootsNostrConnectPermissions { + requested_permissions: &Permissions, + ) -> Permissions { let mut filtered = Vec::new(); for permission in requested_permissions.as_slice() { - if permission.method == RadrootsNostrConnectMethod::SignEvent + if permission.method == Method::SignEvent && permission.parameter.is_none() && !self.allowed_sign_event_kinds.is_empty() { for kind in &self.allowed_sign_event_kinds { - let candidate = RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - format!("kind:{kind}"), - ); + let candidate = + Permission::with_parameter(Method::SignEvent, format!("kind:{kind}")); if self.permission_within_policy(&candidate) { filtered.push(candidate); } @@ -156,8 +150,8 @@ impl MycPolicyContext { pub fn validate_operator_grants( &self, - granted_permissions: RadrootsNostrConnectPermissions, - ) -> Result<RadrootsNostrConnectPermissions, MycError> { + granted_permissions: Permissions, + ) -> Result<Permissions, MycError> { let granted_permissions = normalize_permissions(granted_permissions); let invalid_permissions = granted_permissions .as_slice() @@ -180,7 +174,7 @@ impl MycPolicyContext { &self, backend: &B, connection: &RadrootsNostrSignerConnectionRecord, - request_message: &RadrootsNostrConnectRequestMessage, + request_message: &RequestMessage, ) -> Result<Option<String>, MycError> { if self.client_is_denied(&connection.client_public_key) { return Ok(Some("client public key denied by policy".to_owned())); @@ -253,10 +247,8 @@ impl MycPolicyContext { .contains(&client_public_key.to_hex()) } - fn permission_within_policy(&self, permission: &RadrootsNostrConnectPermission) -> bool { - if permission.method == RadrootsNostrConnectMethod::SignEvent - && !self.allowed_sign_event_kinds.is_empty() - { + fn permission_within_policy(&self, permission: &Permission) -> bool { + if permission.method == Method::SignEvent && !self.allowed_sign_event_kinds.is_empty() { let Some(kind) = permission .parameter .as_deref() @@ -279,10 +271,10 @@ impl MycPolicyContext { .any(|ceiling| permission_within_ceiling(permission, ceiling)) } - fn request_denied_reason(&self, request: &RadrootsNostrConnectRequest) -> Option<String> { + fn request_denied_reason(&self, request: &Request) -> Option<String> { if self.permission_ceiling.is_empty() && (self.allowed_sign_event_kinds.is_empty() - || !matches!(request, RadrootsNostrConnectRequest::SignEvent(_))) + || !matches!(request, Request::SignEvent(_))) { return None; } @@ -301,7 +293,7 @@ impl MycPolicyContext { fn request_uses_automatic_auth( &self, connection: &RadrootsNostrSignerConnectionRecord, - request: &RadrootsNostrConnectRequest, + request: &Request, ) -> bool { self.automatic_auth_enabled_for_connection(connection) && request_requires_auth(request) } @@ -309,7 +301,7 @@ impl MycPolicyContext { fn should_require_fresh_auth( &self, connection: &RadrootsNostrSignerConnectionRecord, - request: &RadrootsNostrConnectRequest, + request: &Request, ) -> bool { if !self.request_uses_automatic_auth(connection, request) { return false; @@ -460,17 +452,11 @@ impl<B: RadrootsNostrSignerBackend> RadrootsNostrSignerNip46Policy<B> for MycPol self.approval_requirement_for_client(client_public_key) } - fn filtered_requested_permissions( - &self, - requested_permissions: &RadrootsNostrConnectPermissions, - ) -> RadrootsNostrConnectPermissions { + fn filtered_requested_permissions(&self, requested_permissions: &Permissions) -> Permissions { self.filtered_requested_permissions(requested_permissions) } - fn auto_granted_permissions( - &self, - requested_permissions: &RadrootsNostrConnectPermissions, - ) -> RadrootsNostrConnectPermissions { + fn auto_granted_permissions(&self, requested_permissions: &Permissions) -> Permissions { self.auto_granted_permissions(requested_permissions) } @@ -478,7 +464,7 @@ impl<B: RadrootsNostrSignerBackend> RadrootsNostrSignerNip46Policy<B> for MycPol &self, backend: &B, connection: &RadrootsNostrSignerConnectionRecord, - request_message: &RadrootsNostrConnectRequestMessage, + request_message: &RequestMessage, ) -> Result<Option<String>, crate::signer::prelude::RadrootsNostrSignerError> { self.prepare_request(backend, connection, request_message) .map_err(myc_policy_signer_error) @@ -513,9 +499,7 @@ impl MycPolicyRateLimiter { } } -fn normalize_permissions( - permissions: RadrootsNostrConnectPermissions, -) -> RadrootsNostrConnectPermissions { +fn normalize_permissions(permissions: Permissions) -> Permissions { let mut permissions = permissions.into_vec(); permissions.sort(); permissions.dedup(); @@ -546,46 +530,27 @@ fn normalize_public_key_hex(value: &str) -> Result<String, MycError> { Ok(public_key.to_hex()) } -fn required_permission_for_request( - request: &RadrootsNostrConnectRequest, -) -> Option<RadrootsNostrConnectPermission> { +fn required_permission_for_request(request: &Request) -> Option<Permission> { match request { - RadrootsNostrConnectRequest::Connect { .. } - | RadrootsNostrConnectRequest::GetPublicKey - | RadrootsNostrConnectRequest::GetSessionCapability - | RadrootsNostrConnectRequest::Ping - | RadrootsNostrConnectRequest::Logout => None, - RadrootsNostrConnectRequest::SignEvent(unsigned_event) => { - Some(RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - format!("kind:{}", unsigned_event.kind()), - )) - } - RadrootsNostrConnectRequest::Nip04Encrypt { .. } => Some( - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), - ), - RadrootsNostrConnectRequest::Nip04Decrypt { .. } => Some( - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Decrypt), - ), - RadrootsNostrConnectRequest::Nip44Encrypt { .. } => Some( - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip44Encrypt), - ), - RadrootsNostrConnectRequest::Nip44Decrypt { .. } => Some( - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip44Decrypt), - ), - RadrootsNostrConnectRequest::SwitchRelays => Some(RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::SwitchRelays, + Request::Connect { .. } + | Request::GetPublicKey + | Request::GetSessionCapability + | Request::Ping + | Request::Logout => None, + Request::SignEvent(unsigned_event) => Some(Permission::with_parameter( + Method::SignEvent, + format!("kind:{}", unsigned_event.kind()), )), - RadrootsNostrConnectRequest::Custom { method, .. } => { - Some(RadrootsNostrConnectPermission::new(method.clone())) - } + Request::Nip04Encrypt { .. } => Some(Permission::new(Method::Nip04Encrypt)), + Request::Nip04Decrypt { .. } => Some(Permission::new(Method::Nip04Decrypt)), + Request::Nip44Encrypt { .. } => Some(Permission::new(Method::Nip44Encrypt)), + Request::Nip44Decrypt { .. } => Some(Permission::new(Method::Nip44Decrypt)), + Request::SwitchRelays => Some(Permission::new(Method::SwitchRelays)), + Request::Custom { method, .. } => Some(Permission::new(method.clone())), } } -fn permission_within_ceiling( - permission: &RadrootsNostrConnectPermission, - ceiling: &RadrootsNostrConnectPermission, -) -> bool { +fn permission_within_ceiling(permission: &Permission, ceiling: &Permission) -> bool { if permission.method != ceiling.method { return false; } @@ -595,11 +560,11 @@ fn permission_within_ceiling( permission.parameter.as_deref(), ceiling.parameter.as_deref(), ) { - (RadrootsNostrConnectMethod::SignEvent, _, None) => true, - (RadrootsNostrConnectMethod::SignEvent, Some(parameter), Some(ceiling_parameter)) => { + (Method::SignEvent, _, None) => true, + (Method::SignEvent, Some(parameter), Some(ceiling_parameter)) => { sign_event_parameter_eq(parameter, ceiling_parameter) } - (RadrootsNostrConnectMethod::SignEvent, None, Some(_)) => false, + (Method::SignEvent, None, Some(_)) => false, (_, _, None) => true, (_, Some(parameter), Some(ceiling_parameter)) => parameter == ceiling_parameter, (_, None, Some(_)) => false, @@ -618,14 +583,14 @@ fn parse_sign_event_kind_parameter(value: &str) -> Option<u16> { .ok() } -fn request_requires_auth(request: &RadrootsNostrConnectRequest) -> bool { +fn request_requires_auth(request: &Request) -> bool { !matches!( request, - RadrootsNostrConnectRequest::Connect { .. } - | RadrootsNostrConnectRequest::GetPublicKey - | RadrootsNostrConnectRequest::GetSessionCapability - | RadrootsNostrConnectRequest::Ping - | RadrootsNostrConnectRequest::Logout + Request::Connect { .. } + | Request::GetPublicKey + | Request::GetSessionCapability + | Request::Ping + | Request::Logout ) } @@ -679,10 +644,8 @@ mod tests { RadrootsNostrSignerManager, }; use nostr::PublicKey; - use radroots_nostr_connect::prelude::{ - RadrootsNostrConnectMethod, RadrootsNostrConnectPermission, - RadrootsNostrConnectPermissions, RadrootsNostrConnectRequest, - RadrootsNostrConnectRequestMessage, + use radroots_nostr_connect::{ + Method, Permission, Request, message::RequestMessage, permission::Permissions, }; use serde_json::json; use std::thread; @@ -729,11 +692,7 @@ mod tests { .to_public(), ) .with_requested_permissions( - vec![RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - "kind:1", - )] - .into(), + vec![Permission::with_parameter(Method::SignEvent, "kind:1")].into(), ) .with_approval_requirement(RadrootsNostrSignerApprovalRequirement::NotRequired), ) @@ -789,11 +748,8 @@ mod tests { fn auto_granted_permissions_apply_policy_ceiling_and_kind_limits() { let config = MycPolicyConfig { permission_ceiling: vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), - RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - "kind:1", - ), + Permission::new(Method::Nip04Encrypt), + Permission::with_parameter(Method::SignEvent, "kind:1"), ] .into(), allowed_sign_event_kinds: vec![1], @@ -801,13 +757,10 @@ mod tests { }; let policy = MycPolicyContext::from_config(&config).expect("policy"); - let requested_permissions: RadrootsNostrConnectPermissions = vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::SignEvent), - RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - "kind:2", - ), + let requested_permissions: Permissions = vec![ + Permission::new(Method::Nip04Encrypt), + Permission::new(Method::SignEvent), + Permission::with_parameter(Method::SignEvent, "kind:2"), ] .into(); let filtered = policy.auto_granted_permissions(&requested_permissions); @@ -833,10 +786,7 @@ mod tests { .prepare_request( &backend, &connection, - &RadrootsNostrConnectRequestMessage::new( - "request-1", - RadrootsNostrConnectRequest::SignEvent(unsigned_event(2)), - ), + &RequestMessage::new("request-1", Request::SignEvent(unsigned_event(2))), ) .expect("prepare request"); @@ -849,20 +799,13 @@ mod tests { #[test] fn validate_operator_grants_rejects_out_of_policy_permissions() { let config = MycPolicyConfig { - permission_ceiling: RadrootsNostrConnectPermissions::from(vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), - ]), + permission_ceiling: Permissions::from(vec![Permission::new(Method::Nip04Encrypt)]), ..MycPolicyConfig::default() }; let policy = MycPolicyContext::from_config(&config).expect("policy"); let error = policy - .validate_operator_grants( - vec![RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::Nip44Encrypt, - )] - .into(), - ) + .validate_operator_grants(vec![Permission::new(Method::Nip44Encrypt)].into()) .expect_err("grant outside ceiling"); assert!( error @@ -902,10 +845,7 @@ mod tests { .prepare_request( &backend, &connection, - &RadrootsNostrConnectRequestMessage::new( - "request-1", - RadrootsNostrConnectRequest::SignEvent(unsigned_event(1)), - ), + &RequestMessage::new("request-1", Request::SignEvent(unsigned_event(1))), ) .expect("prepare request"); @@ -952,7 +892,7 @@ mod tests { .record_request( &connection.connection_id, "request-0", - RadrootsNostrConnectMethod::SignEvent, + Method::SignEvent, crate::signer::prelude::RadrootsNostrSignerRequestDecision::Allowed, None, ) @@ -967,10 +907,7 @@ mod tests { .prepare_request( &backend, &connection, - &RadrootsNostrConnectRequestMessage::new( - "request-1", - RadrootsNostrConnectRequest::SignEvent(unsigned_event(1)), - ), + &RequestMessage::new("request-1", Request::SignEvent(unsigned_event(1))), ) .expect("prepare request"); diff --git a/src/transport/nip46.rs b/src/transport/nip46.rs @@ -13,10 +13,7 @@ use crate::signer::prelude::{ RadrootsNostrSignerRequestDecision, RadrootsNostrSignerRequestEvaluation, RadrootsNostrSignerRequestId, RadrootsNostrSignerSessionLookup, RadrootsNostrSignerWorkflowId, }; -use radroots_nostr_connect::prelude::{ - RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectRequestMessage, - RadrootsNostrConnectResponse, -}; +use radroots_nostr_connect::{Response, message::RPC_KIND, message::RequestMessage}; use tokio::sync::broadcast; use crate::app::MycSignerContext; @@ -202,8 +199,8 @@ impl MycNip46Handler { pub fn parse_request_event( &self, event: &RadrootsNostrEvent, - ) -> Result<RadrootsNostrConnectRequestMessage, MycError> { - if event.kind != RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND) { + ) -> Result<RequestMessage, MycError> { + if event.kind != RadrootsNostrKind::Custom(RPC_KIND) { return Err(MycError::InvalidOperation( "NIP-46 request event has the wrong kind".to_owned(), )); @@ -232,7 +229,7 @@ impl MycNip46Handler { &self, client_public_key: RadrootsNostrPublicKey, request_id: impl Into<String>, - response: RadrootsNostrConnectResponse, + response: Response, ) -> Result<crate::nostr_contract::RadrootsNostrGenericEventBuilder, MycError> { self.handler .build_response_event(client_public_key, request_id, response) @@ -242,11 +239,11 @@ impl MycNip46Handler { pub(crate) fn handle_request( &self, client_public_key: RadrootsNostrPublicKey, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, ) -> Result<MycNip46HandledOutcome, MycError> { if matches!( &request_message.request, - radroots_nostr_connect::prelude::RadrootsNostrConnectRequest::Logout + radroots_nostr_connect::Request::Logout ) { return self.handle_logout_request(client_public_key, request_message); } @@ -258,26 +255,22 @@ impl MycNip46Handler { fn handle_logout_request( &self, client_public_key: RadrootsNostrPublicKey, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, ) -> Result<MycNip46HandledOutcome, MycError> { let manager = self.signer.load_signer_manager()?; let connection = match manager.lookup_session(&client_public_key, None)? { RadrootsNostrSignerSessionLookup::Connection(connection) => *connection, RadrootsNostrSignerSessionLookup::None => { - return Ok(MycNip46HandledOutcome::respond( - RadrootsNostrConnectResponse::Error { - result: None, - error: "unauthorized".to_owned(), - }, - )); + return Ok(MycNip46HandledOutcome::respond(Response::Error { + result: None, + error: "unauthorized".to_owned(), + })); } RadrootsNostrSignerSessionLookup::Ambiguous(_) => { - return Ok(MycNip46HandledOutcome::respond( - RadrootsNostrConnectResponse::Error { - result: None, - error: "ambiguous client sessions".to_owned(), - }, - )); + return Ok(MycNip46HandledOutcome::respond(Response::Error { + result: None, + error: "ambiguous client sessions".to_owned(), + })); } }; if connection.status != RadrootsNostrSignerConnectionStatus::Active { @@ -292,7 +285,7 @@ impl MycNip46Handler { return Ok(MycNip46HandledOutcome::new( RadrootsNostrSignerHandledRequest::respond_for_connection( Some(connection.connection_id), - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: reason, }, @@ -311,7 +304,7 @@ impl MycNip46Handler { Ok(MycNip46HandledOutcome::new( RadrootsNostrSignerHandledRequest::respond_for_connection( Some(connection.connection_id), - RadrootsNostrConnectResponse::LogoutAcknowledged, + Response::LogoutAcknowledged, ), Some(audit), )) @@ -321,8 +314,8 @@ impl MycNip46Handler { fn handle_request_response( &self, client_public_key: RadrootsNostrPublicKey, - request_message: RadrootsNostrConnectRequestMessage, - ) -> Result<RadrootsNostrConnectResponse, MycError> { + request_message: RequestMessage, + ) -> Result<Response, MycError> { match self.handle_request(client_public_key, request_message)? { MycNip46HandledOutcome { handled_request: RadrootsNostrSignerHandledRequest::Respond { response, .. }, @@ -339,7 +332,7 @@ impl MycNip46Handler { pub(crate) fn handle_authorized_request_evaluation( &self, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, evaluation: RadrootsNostrSignerRequestEvaluation, ) -> Result<MycNip46HandledOutcome, MycError> { self.handler @@ -401,7 +394,7 @@ impl MycNip46Service { continue; }; let event = *event; - if event.kind != RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND) { + if event.kind != RadrootsNostrKind::Custom(RPC_KIND) { continue; } @@ -422,7 +415,7 @@ impl MycNip46Service { Ok(handled_outcome) => handled_outcome, Err(error) => { tracing::warn!(error = %error, "failed to handle NIP-46 request"); - MycNip46HandledOutcome::respond(RadrootsNostrConnectResponse::Error { + MycNip46HandledOutcome::respond(Response::Error { result: None, error: error.to_string(), }) @@ -441,10 +434,9 @@ impl MycNip46Service { ); continue; }; - let revoke_logout_connection = - matches!(&response, RadrootsNostrConnectResponse::LogoutAcknowledged) - .then(|| connection_id.clone()) - .flatten(); + let revoke_logout_connection = matches!(&response, Response::LogoutAcknowledged) + .then(|| connection_id.clone()) + .flatten(); let response_event = self.handler @@ -843,11 +835,9 @@ mod tests { use nostr::nips::nip44::Version; use nostr::{EventBuilder, Keys, PublicKey, SecretKey, Timestamp}; use radroots_nostr_connect::message::UnsignedEvent; - use radroots_nostr_connect::prelude::{ - RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectMethod, - RadrootsNostrConnectPermission, RadrootsNostrConnectRequest, - RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse, - RadrootsNostrConnectResponseEnvelope, + use radroots_nostr_connect::{ + Method, Permission, Request, Response, + message::{RPC_KIND, RequestMessage, ResponseEnvelope}, }; use serde_json::json; @@ -911,16 +901,13 @@ mod tests { Keys::new(secret) } - fn request_event( - handler: &MycNip46Handler, - request: RadrootsNostrConnectRequestMessage, - ) -> nostr::Event { + fn request_event(handler: &MycNip46Handler, request: RequestMessage) -> nostr::Event { request_event_with_client_keys(handler, request, &client_keys()) } fn request_event_with_client_keys( handler: &MycNip46Handler, - request: RadrootsNostrConnectRequestMessage, + request: RequestMessage, client_keys: &Keys, ) -> nostr::Event { let payload = serde_json::to_string(&request).expect("serialize request"); @@ -938,22 +925,16 @@ mod tests { Version::V2, ) .expect("encrypt"); - EventBuilder::new( - radroots_nostr_kind(RADROOTS_NOSTR_CONNECT_RPC_KIND), - ciphertext, - ) - .tags(vec![RadrootsNostrTag::public_key( - handler.signer.signer_identity().public_key(), - )]) - .sign_with_keys(client_keys) - .expect("sign request") + EventBuilder::new(radroots_nostr_kind(RPC_KIND), ciphertext) + .tags(vec![RadrootsNostrTag::public_key( + handler.signer.signer_identity().public_key(), + )]) + .sign_with_keys(client_keys) + .expect("sign request") } - fn sign_event_permission(kind: u16) -> RadrootsNostrConnectPermission { - RadrootsNostrConnectPermission::with_parameter( - RadrootsNostrConnectMethod::SignEvent, - format!("kind:{kind}"), - ) + fn sign_event_permission(kind: u16) -> Permission { + Permission::with_parameter(Method::SignEvent, format!("kind:{kind}")) } fn unsigned_event(pubkey: PublicKey, kind: u16, content: &str) -> UnsignedEvent { @@ -973,14 +954,14 @@ mod tests { fn connect_with_permissions( handler: &MycNip46Handler, runtime: &MycRuntime, - requested_permissions: Vec<RadrootsNostrConnectPermission>, + requested_permissions: Vec<Permission>, ) { handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1012,15 +993,14 @@ mod tests { fn parse_and_build_nip46_envelopes_roundtrip() { let runtime = runtime(); let handler = handler(&runtime); - let request = - RadrootsNostrConnectRequestMessage::new("req-1", RadrootsNostrConnectRequest::Ping); + let request = RequestMessage::new("req-1", Request::Ping); let event = request_event(&handler, request.clone()); let parsed = handler.parse_request_event(&event).expect("parse request"); assert_eq!(parsed, request); let response_builder = handler - .build_response_event(event.pubkey, "req-1", RadrootsNostrConnectResponse::Pong) + .build_response_event(event.pubkey, "req-1", Response::Pong) .expect("response builder"); let response_event = runtime .signer_identity() @@ -1032,14 +1012,10 @@ mod tests { &response_event.content, ) .expect("decrypt response"); - let envelope: RadrootsNostrConnectResponseEnvelope = - serde_json::from_str(&decrypted).expect("parse envelope"); - let parsed = RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Ping.method(), - envelope, - ) - .expect("parse response"); - assert_eq!(parsed, RadrootsNostrConnectResponse::Pong); + let envelope: ResponseEnvelope = serde_json::from_str(&decrypted).expect("parse envelope"); + let parsed = + Response::from_envelope(&Request::Ping.method(), envelope).expect("parse response"); + assert_eq!(parsed, Response::Pong); } #[test] @@ -1059,9 +1035,9 @@ mod tests { let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1074,10 +1050,7 @@ mod tests { ) .expect("connect response"); - assert_eq!( - response, - RadrootsNostrConnectResponse::ConnectSecretEcho("s3cr3t".to_owned()) - ); + assert_eq!(response, Response::ConnectSecretEcho("s3cr3t".to_owned())); let connections = runtime .signer_manager() .expect("manager") @@ -1104,9 +1077,9 @@ mod tests { let response = handler .handle_request_response( denied_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1121,7 +1094,7 @@ mod tests { assert_eq!( response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "client public key denied by policy".to_owned(), } @@ -1144,9 +1117,9 @@ mod tests { let first = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect-1", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1161,9 +1134,9 @@ mod tests { let second = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect-2", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1176,10 +1149,7 @@ mod tests { ) .expect("second connect response"); - assert_eq!( - first, - RadrootsNostrConnectResponse::ConnectSecretEcho("s3cr3t".to_owned()) - ); + assert_eq!(first, Response::ConnectSecretEcho("s3cr3t".to_owned())); assert_eq!(second, first); } @@ -1190,9 +1160,9 @@ mod tests { let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1204,10 +1174,7 @@ mod tests { ), ) .expect("connect response"); - assert_eq!( - response, - RadrootsNostrConnectResponse::ConnectSecretEcho("s3cr3t".to_owned()) - ); + assert_eq!(response, Response::ConnectSecretEcho("s3cr3t".to_owned())); let connection = runtime .signer_manager() @@ -1226,9 +1193,9 @@ mod tests { let ignored = handler .handle_request( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect-reused", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1265,9 +1232,9 @@ mod tests { let first = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect-1", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1279,14 +1246,14 @@ mod tests { ), ) .expect("first connect response"); - assert_eq!(first, RadrootsNostrConnectResponse::ConnectAcknowledged); + assert_eq!(first, Response::ConnectAcknowledged); let second = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect-2", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1300,7 +1267,7 @@ mod tests { .expect("second connect response"); assert!(matches!( second, - RadrootsNostrConnectResponse::Error { error, .. } + Response::Error { error, .. } if error.contains("connect attempts throttled by policy") )); @@ -1316,9 +1283,9 @@ mod tests { let third = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect-3", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1330,7 +1297,7 @@ mod tests { ), ) .expect("third connect response"); - assert_eq!(third, RadrootsNostrConnectResponse::ConnectAcknowledged); + assert_eq!(third, Response::ConnectAcknowledged); } #[test] @@ -1341,9 +1308,9 @@ mod tests { let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1356,7 +1323,7 @@ mod tests { ) .expect("connect response"); - assert_eq!(response, RadrootsNostrConnectResponse::ConnectAcknowledged); + assert_eq!(response, Response::ConnectAcknowledged); let connection = runtime .signer_manager() .expect("manager") @@ -1384,15 +1351,12 @@ mod tests { let pending_response = pending_handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( - "req-pending-logout", - RadrootsNostrConnectRequest::Logout, - ), + RequestMessage::new("req-pending-logout", Request::Logout), ) .expect("pending logout response"); assert_eq!( pending_response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "connection is pending".to_owned(), } @@ -1404,16 +1368,10 @@ mod tests { let active_response = active_handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( - "req-active-logout", - RadrootsNostrConnectRequest::Logout, - ), + RequestMessage::new("req-active-logout", Request::Logout), ) .expect("active logout response"); - assert_eq!( - active_response, - RadrootsNostrConnectResponse::LogoutAcknowledged - ); + assert_eq!(active_response, Response::LogoutAcknowledged); assert_eq!( connection_for(&active_runtime, client_keys().public_key()).status, RadrootsNostrSignerConnectionStatus::Active @@ -1428,7 +1386,7 @@ mod tests { let runtime = runtime_with_config(MycConnectionApproval::ExplicitUser, |config| { config.policy.trusted_client_pubkeys = vec![trusted_client_keys.public_key().to_hex()]; config.policy.permission_ceiling = vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), + Permission::new(Method::Nip04Encrypt), sign_event_permission(1), ] .into(); @@ -1439,21 +1397,17 @@ mod tests { let response = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() .public_key(), secret: None, requested_permissions: vec![ - RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::Nip04Encrypt, - ), - RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::SignEvent, - ), + Permission::new(Method::Nip04Encrypt), + Permission::new(Method::SignEvent), sign_event_permission(7), ] .into(), @@ -1463,7 +1417,7 @@ mod tests { ) .expect("connect response"); - assert_eq!(response, RadrootsNostrConnectResponse::ConnectAcknowledged); + assert_eq!(response, Response::ConnectAcknowledged); let connection = connection_for(&runtime, trusted_client_keys.public_key()); assert_eq!( connection.granted_permissions().to_string(), @@ -1492,9 +1446,9 @@ mod tests { let _ = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1510,9 +1464,9 @@ mod tests { let first = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-1", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "first", @@ -1522,7 +1476,7 @@ mod tests { .expect("first sign request"); assert_eq!( first, - RadrootsNostrConnectResponse::AuthUrl("https://auth.example/challenge".to_owned()) + Response::AuthUrl("https://auth.example/challenge".to_owned()) ); let connection = connection_for(&runtime, trusted_client_keys.public_key()); @@ -1535,9 +1489,9 @@ mod tests { let second = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-2", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "second", @@ -1545,19 +1499,16 @@ mod tests { ), ) .expect("second sign request"); - assert!(matches!( - second, - RadrootsNostrConnectResponse::SignedEvent(_) - )); + assert!(matches!(second, Response::SignedEvent(_))); std::thread::sleep(std::time::Duration::from_secs(2)); let third = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-3", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "third", @@ -1567,7 +1518,7 @@ mod tests { .expect("third sign request"); assert_eq!( third, - RadrootsNostrConnectResponse::AuthUrl("https://auth.example/challenge".to_owned()) + Response::AuthUrl("https://auth.example/challenge".to_owned()) ); } @@ -1588,9 +1539,9 @@ mod tests { let _ = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1606,9 +1557,9 @@ mod tests { let first = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-1", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "first", @@ -1618,7 +1569,7 @@ mod tests { .expect("first sign request"); assert_eq!( first, - RadrootsNostrConnectResponse::AuthUrl("https://auth.example/challenge".to_owned()) + Response::AuthUrl("https://auth.example/challenge".to_owned()) ); let connection = connection_for(&runtime, trusted_client_keys.public_key()); @@ -1633,9 +1584,9 @@ mod tests { let second = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-2", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "second", @@ -1645,7 +1596,7 @@ mod tests { .expect("second sign request"); assert_eq!( second, - RadrootsNostrConnectResponse::AuthUrl("https://auth.example/challenge".to_owned()) + Response::AuthUrl("https://auth.example/challenge".to_owned()) ); } @@ -1668,9 +1619,9 @@ mod tests { let _ = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1686,9 +1637,9 @@ mod tests { let first = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-1", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "first", @@ -1698,7 +1649,7 @@ mod tests { .expect("first sign request"); assert_eq!( first, - RadrootsNostrConnectResponse::AuthUrl("https://auth.example/challenge".to_owned()) + Response::AuthUrl("https://auth.example/challenge".to_owned()) ); std::thread::sleep(std::time::Duration::from_secs(2)); @@ -1706,9 +1657,9 @@ mod tests { let second = handler .handle_request_response( trusted_client_keys.public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign-2", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "second", @@ -1718,7 +1669,7 @@ mod tests { .expect("second sign request"); assert!(matches!( second, - RadrootsNostrConnectResponse::Error { error, .. } + Response::Error { error, .. } if error.contains("auth challenge issuance throttled by policy") )); } @@ -1730,18 +1681,15 @@ mod tests { handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() .public_key(), secret: None, - requested_permissions: vec![RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::SwitchRelays, - )] - .into(), + requested_permissions: vec![Permission::new(Method::SwitchRelays)].into(), client_metadata: None, }, ), @@ -1751,42 +1699,31 @@ mod tests { let public_key = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( - "req-pubkey", - RadrootsNostrConnectRequest::GetPublicKey, - ), + RequestMessage::new("req-pubkey", Request::GetPublicKey), ) .expect("get public key"); assert_eq!( public_key, - RadrootsNostrConnectResponse::UserPublicKey( - runtime.user_identity().public_identity().public_key() - ) + Response::UserPublicKey(runtime.user_identity().public_identity().public_key()) ); let pong = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( - "req-ping", - RadrootsNostrConnectRequest::Ping, - ), + RequestMessage::new("req-ping", Request::Ping), ) .expect("ping"); - assert_eq!(pong, RadrootsNostrConnectResponse::Pong); + assert_eq!(pong, Response::Pong); let relays = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( - "req-switch", - RadrootsNostrConnectRequest::SwitchRelays, - ), + RequestMessage::new("req-switch", Request::SwitchRelays), ) .expect("switch relays"); assert_eq!( relays, - RadrootsNostrConnectResponse::RelayList( + Response::RelayList( runtime .transport() .expect("transport") @@ -1802,16 +1739,13 @@ mod tests { let capability = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( - "req-capability", - RadrootsNostrConnectRequest::GetSessionCapability, - ), + RequestMessage::new("req-capability", Request::GetSessionCapability), ) .expect("get session capability"); assert_eq!( capability, - RadrootsNostrConnectResponse::RemoteSessionCapability( - radroots_nostr_connect::prelude::RadrootsNostrConnectRemoteSessionCapability { + Response::RemoteSessionCapability( + radroots_nostr_connect::message::RemoteSessionCapability { user_public_key: runtime.user_identity().public_identity().public_key(), relays: runtime .transport() @@ -1823,10 +1757,7 @@ mod tests { .expect("relay") }) .collect(), - permissions: vec![RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::SwitchRelays, - )] - .into(), + permissions: vec![Permission::new(Method::SwitchRelays,)].into(), }, ) ); @@ -1839,9 +1770,9 @@ mod tests { handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: runtime .signer_identity() .public_identity() @@ -1877,9 +1808,9 @@ mod tests { let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "hello world", @@ -1888,7 +1819,7 @@ mod tests { ) .expect("sign event"); - let RadrootsNostrConnectResponse::SignedEvent(event) = response else { + let Response::SignedEvent(event) = response else { panic!("unexpected sign_event response"); }; let event: nostr::Event = serde_json::from_str(&event.as_json()).expect("signed event"); @@ -1907,9 +1838,9 @@ mod tests { let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( runtime.user_identity().public_key(), 1, "hello world", @@ -1920,7 +1851,7 @@ mod tests { assert_eq!( response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "unauthorized sign_event".to_owned(), } @@ -1936,9 +1867,9 @@ mod tests { let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-sign", - RadrootsNostrConnectRequest::SignEvent(unsigned_event( + Request::SignEvent(unsigned_event( client_keys().public_key(), 1, "hello world", @@ -1949,7 +1880,7 @@ mod tests { assert_eq!( response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "sign_event pubkey does not match the managed user identity".to_owned(), } @@ -1964,17 +1895,17 @@ mod tests { &handler, &runtime, vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Encrypt), - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip04Decrypt), + Permission::new(Method::Nip04Encrypt), + Permission::new(Method::Nip04Decrypt), ], ); let encrypt_response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-nip04-encrypt", - RadrootsNostrConnectRequest::Nip04Encrypt { + Request::Nip04Encrypt { public_key: radroots_nostr::key::public_key_from_nostr( client_keys().public_key(), ) @@ -1984,7 +1915,7 @@ mod tests { ), ) .expect("nip04 encrypt"); - let RadrootsNostrConnectResponse::Nip04Encrypt(ciphertext) = encrypt_response else { + let Response::Nip04Encrypt(ciphertext) = encrypt_response else { panic!("unexpected nip04 encrypt response"); }; assert_eq!( @@ -2006,9 +1937,9 @@ mod tests { let decrypt_response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-nip04-decrypt", - RadrootsNostrConnectRequest::Nip04Decrypt { + Request::Nip04Decrypt { public_key: radroots_nostr::key::public_key_from_nostr( client_keys().public_key(), ) @@ -2020,7 +1951,7 @@ mod tests { .expect("nip04 decrypt"); assert_eq!( decrypt_response, - RadrootsNostrConnectResponse::Nip04Decrypt("hello to myc".to_owned()) + Response::Nip04Decrypt("hello to myc".to_owned()) ); } @@ -2032,17 +1963,17 @@ mod tests { &handler, &runtime, vec![ - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip44Encrypt), - RadrootsNostrConnectPermission::new(RadrootsNostrConnectMethod::Nip44Decrypt), + Permission::new(Method::Nip44Encrypt), + Permission::new(Method::Nip44Decrypt), ], ); let encrypt_response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-nip44-encrypt", - RadrootsNostrConnectRequest::Nip44Encrypt { + Request::Nip44Encrypt { public_key: radroots_nostr::key::public_key_from_nostr( client_keys().public_key(), ) @@ -2052,7 +1983,7 @@ mod tests { ), ) .expect("nip44 encrypt"); - let RadrootsNostrConnectResponse::Nip44Encrypt(ciphertext) = encrypt_response else { + let Response::Nip44Encrypt(ciphertext) = encrypt_response else { panic!("unexpected nip44 encrypt response"); }; assert_eq!( @@ -2075,9 +2006,9 @@ mod tests { let decrypt_response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-nip44-decrypt", - RadrootsNostrConnectRequest::Nip44Decrypt { + Request::Nip44Decrypt { public_key: radroots_nostr::key::public_key_from_nostr( client_keys().public_key(), ) @@ -2089,7 +2020,7 @@ mod tests { .expect("nip44 decrypt"); assert_eq!( decrypt_response, - RadrootsNostrConnectResponse::Nip44Decrypt("hello to myc".to_owned()) + Response::Nip44Decrypt("hello to myc".to_owned()) ); } @@ -2100,17 +2031,15 @@ mod tests { connect_with_permissions( &handler, &runtime, - vec![RadrootsNostrConnectPermission::new( - RadrootsNostrConnectMethod::Nip04Encrypt, - )], + vec![Permission::new(Method::Nip04Encrypt)], ); let response = handler .handle_request_response( client_keys().public_key(), - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "req-nip04-decrypt", - RadrootsNostrConnectRequest::Nip04Decrypt { + Request::Nip04Decrypt { public_key: radroots_nostr::key::public_key_from_nostr( client_keys().public_key(), ) @@ -2123,7 +2052,7 @@ mod tests { assert_eq!( response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "unauthorized nip04_decrypt".to_owned(), } diff --git a/tests/discovery_cli.rs b/tests/discovery_cli.rs @@ -14,7 +14,7 @@ use myc::nostr_contract::{ }; use nostr::filter::MatchEventOptions; use nostr::{ClientMessage, Event, Filter, JsonUtil, PublicKey, RelayMessage, SubscriptionId}; -use radroots_nostr_connect::prelude::RadrootsNostrConnectUri; +use radroots_nostr_connect::uri::Uri; use serde_json::Value; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, Notify, mpsc, oneshot}; @@ -1169,7 +1169,7 @@ async fn discovery_diff_surfaces_relay_provenance_through_the_cli() -> TestResul let mut bunker_query = url::form_urlencoded::Serializer::new(String::new()); bunker_query.append_pair("relay", relay_a.url()); bunker_query.append_pair("relay", relay_b.url()); - let bunker_uri = RadrootsNostrConnectUri::parse(&format!( + let bunker_uri = Uri::parse(&format!( "bunker://{}?{}", signer_identity.final_public_key(), bunker_query.finish() diff --git a/tests/nip46_e2e.rs b/tests/nip46_e2e.rs @@ -36,10 +36,12 @@ use nostr::{ ClientMessage, Event, EventBuilder, Filter, JsonUtil, Keys, Kind, PublicKey, RelayMessage, SecretKey, SubscriptionId, Tag, Timestamp, UnsignedEvent, }; -use radroots_nostr_connect::prelude::{ - RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectClientMetadata, - RadrootsNostrConnectRequest, RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse, - RadrootsNostrConnectResponseEnvelope, RadrootsNostrConnectUri, +use radroots_nostr_connect::{ + Client, Request, Response, + client::{ClientEvent, EventOutcome, Target}, + message::{RPC_KIND, RequestId, RequestMessage, ResponseEnvelope}, + permission::Permissions, + uri::{ClientMetadata, RelayUrl as ConnectRelayUrl, Uri}, }; use tempfile::TempDir; use tokio::net::{TcpListener, TcpStream}; @@ -76,7 +78,7 @@ fn connect_client_uri( identity: &RadrootsIdentity, relays: &[&str], secret: &str, - metadata: &RadrootsNostrConnectClientMetadata, + metadata: &ClientMetadata, ) -> TestResult<String> { let mut query = url::form_urlencoded::Serializer::new(String::new()); for relay in relays { @@ -100,11 +102,11 @@ fn connect_client_uri( identity.final_public_key(), query.finish() ); - Ok(RadrootsNostrConnectUri::parse(&uri)?.to_string()) + Ok(Uri::parse(&uri)?.to_string()) } const RELAY_EVENT_TIMEOUT: Duration = Duration::from_secs(15); -const EXTERNAL_RESPONSE_TIMEOUT: Duration = Duration::from_secs(15); +const EXTERNAL_RESPONSE_TIMEOUT: Duration = Duration::from_secs(30); const RUNTIME_STATE_TIMEOUT: Duration = Duration::from_secs(15); const POLL_INTERVAL: Duration = Duration::from_millis(25); @@ -603,7 +605,7 @@ fn connect_request_message( request_id: &str, signer_public_key: PublicKey, secret: &str, -) -> RadrootsNostrConnectRequestMessage { +) -> RequestMessage { connect_request_message_with_metadata(request_id, signer_public_key, secret, None) } @@ -611,11 +613,11 @@ fn connect_request_message_with_metadata( request_id: &str, signer_public_key: PublicKey, secret: &str, - client_metadata: Option<RadrootsNostrConnectClientMetadata>, -) -> RadrootsNostrConnectRequestMessage { - RadrootsNostrConnectRequestMessage::new( + client_metadata: Option<ClientMetadata>, +) -> RequestMessage { + RequestMessage::new( request_id, - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: connect_public_key(signer_public_key), secret: Some(secret.to_owned()), requested_permissions: Default::default(), @@ -624,14 +626,14 @@ fn connect_request_message_with_metadata( ) } -fn ping_request_message(request_id: &str) -> RadrootsNostrConnectRequestMessage { - RadrootsNostrConnectRequestMessage::new(request_id, RadrootsNostrConnectRequest::Ping) +fn ping_request_message(request_id: &str) -> RequestMessage { + RequestMessage::new(request_id, Request::Ping) } fn build_request_event( client_identity: &RadrootsIdentity, signer_public_key: PublicKey, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, created_at_unix: u64, ) -> Event { build_request_event_with_recipient( @@ -647,7 +649,7 @@ fn build_request_event_with_recipient( client_identity: &RadrootsIdentity, signer_public_key: PublicKey, recipient_public_key: PublicKey, - request_message: RadrootsNostrConnectRequestMessage, + request_message: RequestMessage, created_at_unix: u64, ) -> Event { let payload = serde_json::to_string(&request_message).expect("request payload"); @@ -674,7 +676,7 @@ fn build_request_event_payload( Version::V2, ) .expect("encrypt request"); - EventBuilder::new(Kind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), ciphertext) + EventBuilder::new(Kind::Custom(RPC_KIND), ciphertext) .tags([Tag::public_key(recipient_public_key)]) .custom_created_at(Timestamp::from(created_at_unix)) .sign_with_keys(client_identity.keys()) @@ -706,7 +708,7 @@ fn build_external_request_event( Version::V2, ) .expect("encrypt external request"); - EventBuilder::new(Kind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), ciphertext) + EventBuilder::new(Kind::Custom(RPC_KIND), ciphertext) .tags([Tag::public_key(signer_public_key)]) .custom_created_at(Timestamp::from(created_at_unix)) .sign_with_keys(client_identity.keys()) @@ -717,7 +719,7 @@ fn build_signer_noise_event(signer_identity: &MycActiveIdentity, created_at_unix signer_identity .sign_protocol_event_builder( RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + RadrootsNostrKind::Custom(RPC_KIND), "non-nip44-signer-noise", ) .custom_created_at(Timestamp::from(created_at_unix)), @@ -730,7 +732,7 @@ fn decrypt_response( client_identity: &RadrootsIdentity, signer_public_key: PublicKey, response_event: &Event, -) -> RadrootsNostrConnectResponseEnvelope { +) -> ResponseEnvelope { let plaintext = nip44::decrypt( client_identity.keys().secret_key(), &signer_public_key, @@ -770,7 +772,13 @@ async fn wait_for_external_response( sleep(POLL_INTERVAL).await; } }) - .await? + .await + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::TimedOut, + format!("timed out waiting for NIP-46 response `{request_id}`"), + ) + })? } async fn publish_external_request_and_wait_for_response( @@ -800,6 +808,72 @@ async fn publish_external_request_and_wait_for_response( .await } +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn packaged_final_client_interoperates_with_myc_server() -> TestResult<()> { + let relay = TestRelay::spawn().await?; + let test_runtime = MycTestRuntime::new(relay.url(), MycConnectionApproval::NotRequired); + let runtime = test_runtime.runtime.clone(); + let signer_public_key = runtime.signer_identity().public_key(); + let final_signer_public_key = connect_public_key(signer_public_key); + let target = Target::try_new( + final_signer_public_key, + vec![ConnectRelayUrl::parse(relay.url())?], + )?; + let client = Client::from_secret( + "3333333333333333333333333333333333333333333333333333333333333333", + target, + )?; + + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let service_runtime = runtime.clone(); + let listener_task = tokio::spawn(async move { + service_runtime + .run_until(async { + let _ = shutdown_rx.await; + }) + .await + }); + relay.wait_for_subscription_count(1).await?; + + let mut connect = client.prepare( + RequestId::parse("final-client-connect")?, + Request::Connect { + remote_signer_public_key: final_signer_public_key, + secret: None, + requested_permissions: Permissions::default(), + client_metadata: None, + }, + )?; + let request = Event::from_json(connect.publication()?.as_json())?; + publish_event(relay.url(), &request).await?; + connect.mark_published()?; + let responses = relay + .wait_for_published_events_by_author(signer_public_key, 1) + .await?; + let response = ClientEvent::from_json(&responses[0].as_json())?; + assert_eq!( + connect.select(&response)?, + EventOutcome::Complete(Box::new(Response::ConnectAcknowledged)) + ); + + let mut ping = client.prepare(RequestId::parse("final-client-ping")?, Request::Ping)?; + let request = Event::from_json(ping.publication()?.as_json())?; + publish_event(relay.url(), &request).await?; + ping.mark_published()?; + let responses = relay + .wait_for_published_events_by_author(signer_public_key, 2) + .await?; + let response = ClientEvent::from_json(&responses[1].as_json())?; + assert_eq!( + ping.select(&response)?, + EventOutcome::Complete(Box::new(Response::Pong)) + ); + + let _ = shutdown_tx.send(()); + listener_task.await??; + Ok(()) +} + fn register_external_client_session( runtime: &MycRuntime, client_public_key: PublicKey, @@ -807,7 +881,7 @@ fn register_external_client_session( permissions: &str, ) -> TestResult<()> { let manager = runtime.signer_manager()?; - let requested_permissions: radroots_nostr_connect::prelude::RadrootsNostrConnectPermissions = + let requested_permissions: radroots_nostr_connect::permission::Permissions = if permissions.trim().is_empty() { Default::default() } else { @@ -1000,8 +1074,8 @@ async fn live_listener_rejects_denied_clients_without_registering_connection() - .await?; let response = decrypt_response(&client_identity, signer_public_key, &response_events[0]); assert_eq!(response.id, "denied-connect"); - let parsed = radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Connect { + let parsed = radroots_nostr_connect::Response::from_envelope( + &Request::Connect { remote_signer_public_key: connect_public_key(signer_public_key), secret: Some("denied-secret".to_owned()), requested_permissions: Default::default(), @@ -1012,7 +1086,7 @@ async fn live_listener_rejects_denied_clients_without_registering_connection() - )?; assert_eq!( parsed, - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::Error { + radroots_nostr_connect::Response::Error { result: None, error: "client public key denied by policy".to_owned(), } @@ -1083,13 +1157,10 @@ async fn live_listener_discards_malformed_and_replayed_request_events() -> TestR ); publish_event(relay.url(), &invalid_request_id).await?; - let malformed_ciphertext = EventBuilder::new( - Kind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), - "not-nip44-ciphertext", - ) - .tags([Tag::public_key(signer_public_key)]) - .custom_created_at(Timestamp::from(base_created_at + 3)) - .sign_with_keys(client_identity.keys())?; + let malformed_ciphertext = EventBuilder::new(Kind::Custom(RPC_KIND), "not-nip44-ciphertext") + .tags([Tag::public_key(signer_public_key)]) + .custom_created_at(Timestamp::from(base_created_at + 3)) + .sign_with_keys(client_identity.keys())?; publish_event(relay.url(), &malformed_ciphertext).await?; sleep(Duration::from_millis(200)).await; @@ -1166,7 +1237,7 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> }); relay.wait_for_subscription_count(1).await?; - let connect_request = RadrootsNostrConnectRequest::Connect { + let connect_request = Request::Connect { remote_signer_public_key: connect_public_key(signer_public_key), secret: None, requested_permissions: "get_public_key,sign_event:1,sign_event:7,switch_relays".parse()?, @@ -1177,7 +1248,7 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> &build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new("policy-connect", connect_request.clone()), + RequestMessage::new("policy-connect", connect_request.clone()), base_created_at, ), ) @@ -1185,14 +1256,11 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> let responses = relay .wait_for_published_events_by_author(signer_public_key, 1) .await?; - let connect_response = RadrootsNostrConnectResponse::from_envelope( + let connect_response = Response::from_envelope( &connect_request.method(), decrypt_response(&client_identity, signer_public_key, &responses[0]), )?; - assert_eq!( - connect_response, - RadrootsNostrConnectResponse::ConnectAcknowledged - ); + assert_eq!(connect_response, Response::ConnectAcknowledged); let connection = runtime .signer_manager()? .list_connections()? @@ -1204,16 +1272,13 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> "get_public_key,sign_event:1,switch_relays" ); - let get_public_key_request = RadrootsNostrConnectRequest::GetPublicKey; + let get_public_key_request = Request::GetPublicKey; publish_event( relay.url(), &build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( - "policy-get-public-key", - get_public_key_request.clone(), - ), + RequestMessage::new("policy-get-public-key", get_public_key_request.clone()), base_created_at + 1, ), ) @@ -1223,26 +1288,23 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> .await?; assert_eq!(responses[1].pubkey, signer_public_key); assert_eq!( - RadrootsNostrConnectResponse::from_envelope( + Response::from_envelope( &get_public_key_request.method(), decrypt_response(&client_identity, signer_public_key, &responses[1]), )?, - RadrootsNostrConnectResponse::UserPublicKey(connect_public_key(user_public_key)) + Response::UserPublicKey(connect_public_key(user_public_key)) ); let unsigned_event = |kind: u16, content: &str| { connect_unsigned_event(user_public_key, base_created_at, kind, content) }; - let allowed_sign_request = RadrootsNostrConnectRequest::SignEvent(unsigned_event(1, "allowed")); + let allowed_sign_request = Request::SignEvent(unsigned_event(1, "allowed")); publish_event( relay.url(), &build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( - "policy-sign-allowed", - allowed_sign_request.clone(), - ), + RequestMessage::new("policy-sign-allowed", allowed_sign_request.clone()), base_created_at + 2, ), ) @@ -1250,27 +1312,24 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> let responses = relay .wait_for_published_events_by_author(signer_public_key, 3) .await?; - let allowed_response = RadrootsNostrConnectResponse::from_envelope( + let allowed_response = Response::from_envelope( &allowed_sign_request.method(), decrypt_response(&client_identity, signer_public_key, &responses[2]), )?; - let RadrootsNostrConnectResponse::SignedEvent(signed_event) = allowed_response else { + let Response::SignedEvent(signed_event) = allowed_response else { panic!("expected signed event response"); }; let signed_event: Event = serde_json::from_str(&signed_event.as_json())?; assert_eq!(signed_event.pubkey, user_public_key); signed_event.verify()?; - let denied_sign_request = RadrootsNostrConnectRequest::SignEvent(unsigned_event(7, "denied")); + let denied_sign_request = Request::SignEvent(unsigned_event(7, "denied")); publish_event( relay.url(), &build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( - "policy-sign-denied", - denied_sign_request.clone(), - ), + RequestMessage::new("policy-sign-denied", denied_sign_request.clone()), base_created_at + 3, ), ) @@ -1279,21 +1338,21 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> .wait_for_published_events_by_author(signer_public_key, 4) .await?; assert!(matches!( - RadrootsNostrConnectResponse::from_envelope( + Response::from_envelope( &denied_sign_request.method(), decrypt_response(&client_identity, signer_public_key, &responses[3]), )?, - RadrootsNostrConnectResponse::Error { error, .. } + Response::Error { error, .. } if error.contains("outside the configured policy ceiling") )); - let switch_request = RadrootsNostrConnectRequest::SwitchRelays; + let switch_request = Request::SwitchRelays; publish_event( relay.url(), &build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new("policy-switch", switch_request.clone()), + RequestMessage::new("policy-switch", switch_request.clone()), base_created_at + 4, ), ) @@ -1302,11 +1361,11 @@ async fn live_listener_enforces_signing_ceiling_and_switch_relay_permission() -> .wait_for_published_events_by_author(signer_public_key, 5) .await?; assert_eq!( - RadrootsNostrConnectResponse::from_envelope( + Response::from_envelope( &switch_request.method(), decrypt_response(&client_identity, signer_public_key, &responses[4]), )?, - RadrootsNostrConnectResponse::RelayList(vec![relay.url().parse()?]) + Response::RelayList(vec![relay.url().parse()?]) ); let _ = shutdown_tx.send(()); @@ -1917,10 +1976,7 @@ async fn live_listener_acknowledges_logout_before_revoking_session() -> TestResu let logout = build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( - "logout-request", - RadrootsNostrConnectRequest::Logout, - ), + RequestMessage::new("logout-request", Request::Logout), base_created_at + 1, ); publish_event(relay.url(), &logout).await?; @@ -1928,14 +1984,8 @@ async fn live_listener_acknowledges_logout_before_revoking_session() -> TestResu .wait_for_published_events_by_author(signer_public_key, 2) .await?; let logout_response = decrypt_response(&client_identity, signer_public_key, &responses[1]); - let logout_response = RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Logout.method(), - logout_response, - )?; - assert_eq!( - logout_response, - RadrootsNostrConnectResponse::LogoutAcknowledged - ); + let logout_response = Response::from_envelope(&Request::Logout.method(), logout_response)?; + assert_eq!(logout_response, Response::LogoutAcknowledged); wait_for_client_connection_status( &runtime, client_identity.public_key(), @@ -1959,10 +2009,7 @@ async fn live_listener_acknowledges_logout_before_revoking_session() -> TestResu let repeated_logout = build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( - "repeated-logout", - RadrootsNostrConnectRequest::Logout, - ), + RequestMessage::new("repeated-logout", Request::Logout), base_created_at + 2, ); publish_event(relay.url(), &repeated_logout).await?; @@ -1971,13 +2018,11 @@ async fn live_listener_acknowledges_logout_before_revoking_session() -> TestResu .await?; let repeated_logout_response = decrypt_response(&client_identity, signer_public_key, &responses[2]); - let repeated_logout_response = RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Logout.method(), - repeated_logout_response, - )?; + let repeated_logout_response = + Response::from_envelope(&Request::Logout.method(), repeated_logout_response)?; assert_eq!( repeated_logout_response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "unauthorized".to_owned(), } @@ -1994,13 +2039,10 @@ async fn live_listener_acknowledges_logout_before_revoking_session() -> TestResu .wait_for_published_events_by_author(signer_public_key, 4) .await?; let ping_response = decrypt_response(&client_identity, signer_public_key, &responses[3]); - let ping_response = RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Ping.method(), - ping_response, - )?; + let ping_response = Response::from_envelope(&Request::Ping.method(), ping_response)?; assert_eq!( ping_response, - RadrootsNostrConnectResponse::Error { + Response::Error { result: None, error: "unauthorized".to_owned(), } @@ -2084,10 +2126,7 @@ async fn failed_logout_publish_is_retried_and_revoked_during_startup_recovery() let logout = build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( - "recovery-logout", - RadrootsNostrConnectRequest::Logout, - ), + RequestMessage::new("recovery-logout", Request::Logout), base_created_at + 1, ); publish_event(relay.url(), &logout).await?; @@ -2129,14 +2168,9 @@ async fn failed_logout_publish_is_retried_and_revoked_during_startup_recovery() .wait_for_published_events_by_author(signer_public_key, 2) .await?; let recovered_response = decrypt_response(&client_identity, signer_public_key, &responses[1]); - let recovered_response = RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Logout.method(), - recovered_response, - )?; - assert_eq!( - recovered_response, - RadrootsNostrConnectResponse::LogoutAcknowledged - ); + let recovered_response = + Response::from_envelope(&Request::Logout.method(), recovered_response)?; + assert_eq!(recovered_response, Response::LogoutAcknowledged); let recovered_connection = restarted_runtime .signer_manager()? .find_connections_by_client_public_key(&client_identity.public_key())? @@ -2181,7 +2215,7 @@ async fn published_logout_acknowledgement_is_finalized_without_republish_on_rest .signer_identity() .sign_protocol_event_builder( RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + RadrootsNostrKind::Custom(RPC_KIND), "published logout acknowledgement fixture", ), "published logout acknowledgement fixture", @@ -2286,7 +2320,7 @@ async fn live_listener_works_with_sqlite_signer_state_and_runtime_audit() -> Tes relay.wait_for_subscription_count(1).await?; - let client_metadata = RadrootsNostrConnectClientMetadata { + let client_metadata = ClientMetadata { requested_permissions: "sign_event:1".parse()?, name: Some(" SQLite Client ".to_owned()), url: Some("https://client.example/".to_owned()), @@ -2477,8 +2511,7 @@ async fn external_nostr_client_recovers_connect_response_after_restart() -> Test .with_relays(vec![relay_url.clone()]) .with_approval_requirement(RadrootsNostrSignerApprovalRequirement::NotRequired), )?; - let response_envelope = - RadrootsNostrConnectResponse::ConnectAcknowledged.into_envelope(connect_request_id)?; + let response_envelope = Response::ConnectAcknowledged.into_envelope(connect_request_id)?; let response_payload = serde_json::to_string(&response_envelope)?; let signer_identity = identity("1111111111111111111111111111111111111111111111111111111111111111"); @@ -2490,7 +2523,7 @@ async fn external_nostr_client_recovers_connect_response_after_restart() -> Test )?; let response_event = runtime.signer_identity().sign_protocol_event_builder( RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + RadrootsNostrKind::Custom(RPC_KIND), response_ciphertext, ) .tags(vec![RadrootsNostrTag::public_key( @@ -2608,7 +2641,7 @@ async fn startup_recovery_republishes_queued_listener_connect_secret_job() -> Te .signer_identity() .sign_protocol_event_builder( RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + RadrootsNostrKind::Custom(RPC_KIND), "startup-recovery", ), "startup recovery", @@ -2707,7 +2740,7 @@ async fn startup_recovery_republishes_queued_connect_accept_job() -> TestResult< .signer_identity() .sign_protocol_event_builder( RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + RadrootsNostrKind::Custom(RPC_KIND), "startup-recovery-connect-accept", ), "startup recovery connect accept", @@ -2812,7 +2845,7 @@ async fn startup_recovery_republishes_queued_auth_replay_job() -> TestResult<()> .signer_identity() .sign_protocol_event_builder( RadrootsNostrGenericEventBuilder::new( - RadrootsNostrKind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + RadrootsNostrKind::Custom(RPC_KIND), "startup-recovery-auth-replay", ), "startup recovery auth replay", @@ -2921,9 +2954,9 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { let connect_request = build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( "trusted-connect", - RadrootsNostrConnectRequest::Connect { + Request::Connect { remote_signer_public_key: connect_public_key(signer_public_key), secret: None, requested_permissions: "sign_event:1".parse().expect("requested permissions"), @@ -2938,29 +2971,28 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { .await?; let connect_response = decrypt_response(&client_identity, signer_public_key, &response_events[0]); - let connect_parsed = - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::Connect { - remote_signer_public_key: connect_public_key(signer_public_key), - secret: None, - requested_permissions: "sign_event:1".parse().expect("requested permissions"), - client_metadata: None, - } - .method(), - connect_response, - )?; + let connect_parsed = radroots_nostr_connect::Response::from_envelope( + &Request::Connect { + remote_signer_public_key: connect_public_key(signer_public_key), + secret: None, + requested_permissions: "sign_event:1".parse().expect("requested permissions"), + client_metadata: None, + } + .method(), + connect_response, + )?; assert_eq!( connect_parsed, - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::ConnectAcknowledged + radroots_nostr_connect::Response::ConnectAcknowledged ); let sign_request = |request_id: &str, created_at_unix| { build_request_event( &client_identity, signer_public_key, - RadrootsNostrConnectRequestMessage::new( + RequestMessage::new( request_id, - RadrootsNostrConnectRequest::SignEvent(connect_unsigned_event( + Request::SignEvent(connect_unsigned_event( runtime.user_identity().public_key(), created_at_unix, 1, @@ -2980,8 +3012,8 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { .wait_for_published_events_by_author(signer_public_key, 2) .await?; let first_auth = decrypt_response(&client_identity, signer_public_key, &response_events[1]); - let first_auth = radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::SignEvent(connect_unsigned_event( + let first_auth = radroots_nostr_connect::Response::from_envelope( + &Request::SignEvent(connect_unsigned_event( runtime.user_identity().public_key(), Timestamp::from(1).as_secs(), 1, @@ -2992,9 +3024,7 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { )?; assert_eq!( first_auth, - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::AuthUrl( - "https://auth.example/challenge".to_owned() - ) + radroots_nostr_connect::Response::AuthUrl("https://auth.example/challenge".to_owned()) ); let connection = runtime @@ -3014,20 +3044,19 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { .await?; let replay_response = decrypt_response(&client_identity, signer_public_key, &response_events[2]); - let replay_parsed = - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::SignEvent(connect_unsigned_event( - runtime.user_identity().public_key(), - Timestamp::from(1).as_secs(), - 1, - "trusted-sign-1", - )) - .method(), - replay_response, - )?; + let replay_parsed = radroots_nostr_connect::Response::from_envelope( + &Request::SignEvent(connect_unsigned_event( + runtime.user_identity().public_key(), + Timestamp::from(1).as_secs(), + 1, + "trusted-sign-1", + )) + .method(), + replay_response, + )?; assert!(matches!( replay_parsed, - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::SignedEvent(_) + radroots_nostr_connect::Response::SignedEvent(_) )); sleep(Duration::from_secs(2)).await; @@ -3041,8 +3070,8 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { .wait_for_published_events_by_author(signer_public_key, 4) .await?; let second_auth = decrypt_response(&client_identity, signer_public_key, &response_events[3]); - let second_auth = radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::from_envelope( - &RadrootsNostrConnectRequest::SignEvent(connect_unsigned_event( + let second_auth = radroots_nostr_connect::Response::from_envelope( + &Request::SignEvent(connect_unsigned_event( runtime.user_identity().public_key(), Timestamp::from(1).as_secs(), 1, @@ -3053,9 +3082,7 @@ async fn trusted_client_reauths_after_authorized_ttl() -> TestResult<()> { )?; assert_eq!( second_auth, - radroots_nostr_connect::prelude::RadrootsNostrConnectResponse::AuthUrl( - "https://auth.example/challenge".to_owned() - ) + radroots_nostr_connect::Response::AuthUrl("https://auth.example/challenge".to_owned()) ); let _ = shutdown_tx.send(()); @@ -3077,7 +3104,7 @@ async fn connect_accept_retries_without_consuming_secret_until_publish_succeeds( .queue_publish_outcomes(signer_public_key, &[false, true]) .await; - let client_metadata = RadrootsNostrConnectClientMetadata { + let client_metadata = ClientMetadata { requested_permissions: Default::default(), name: Some(" Connect Accept Client ".to_owned()), url: Some("https://connect.example/".to_owned()), @@ -3257,7 +3284,7 @@ async fn connect_accept_succeeds_with_any_delivery_policy_when_one_relay_acknowl &client_identity, &[relay_a.url(), relay_b.url()], "delivery-any-secret", - &RadrootsNostrConnectClientMetadata::default(), + &ClientMetadata::default(), )?; let accepted = control::accept_client_uri(&runtime, &client_uri).await?; @@ -3328,7 +3355,7 @@ async fn connect_accept_rejects_when_quorum_delivery_policy_is_not_met() -> Test &client_identity, &[relay_a.url(), relay_b.url()], "delivery-quorum-secret", - &RadrootsNostrConnectClientMetadata::default(), + &ClientMetadata::default(), )?; let error = control::accept_client_uri(&runtime, &client_uri)