radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit 3a8a2ef73f2b99bbe9695d473f408194927206c2
parent dc19ff32ce0798a59024fa98eab911989ce9efe9
Author: triesap <tyson@radroots.org>
Date:   Fri, 27 Mar 2026 19:15:49 +0000

api: stop exposing ad hoc nip46 session internals

Diffstat:
Msrc/core/nip46/session.rs | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/transport/jsonrpc/methods/nip46/session_list.rs | 43+++----------------------------------------
Msrc/transport/jsonrpc/methods/nip46/session_status.rs | 28+++-------------------------
3 files changed, 117 insertions(+), 65 deletions(-)

diff --git a/src/core/nip46/session.rs b/src/core/nip46/session.rs @@ -4,6 +4,7 @@ use std::collections::{HashMap, HashSet}; use std::time::{Duration, Instant}; use std::sync::Arc; +use serde::Serialize; use tokio::sync::Mutex; use radroots_nostr::prelude::{ @@ -30,6 +31,31 @@ pub struct Nip46AuthorizeOutcome { pub pending: Option<PendingNostrRequest>, } +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Nip46SessionRole { + InboundLocalSigner, + OutboundRemoteSigner, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Nip46SessionView { + pub session_id: String, + pub role: Nip46SessionRole, + pub client_pubkey: String, + pub signer_pubkey: String, + pub user_pubkey: Option<String>, + pub relays: Vec<String>, + pub permissions: Vec<String>, + pub name: Option<String>, + pub url: Option<String>, + pub image: Option<String>, + pub auth_required: bool, + pub authorized: bool, + pub auth_url: Option<String>, + pub expires_in_secs: Option<u64>, +} + #[derive(Clone)] pub struct Nip46Session { pub id: String, @@ -178,6 +204,41 @@ impl Nip46Session { .map(|expires_at| expires_at <= Instant::now()) .unwrap_or(false) } + + pub fn role(&self) -> Nip46SessionRole { + if self.client_keys.public_key() == self.remote_signer_pubkey { + Nip46SessionRole::InboundLocalSigner + } else { + Nip46SessionRole::OutboundRemoteSigner + } + } + + pub fn public_view(&self) -> Nip46SessionView { + Nip46SessionView { + session_id: self.id.clone(), + role: self.role(), + client_pubkey: self.client_pubkey.to_hex(), + signer_pubkey: self.remote_signer_pubkey.to_hex(), + user_pubkey: self.user_pubkey.as_ref().map(|pubkey| pubkey.to_hex()), + relays: self.relays.clone(), + permissions: self.perms.clone(), + name: self.name.clone(), + url: self.url.clone(), + image: self.image.clone(), + auth_required: self.auth_required, + authorized: self.authorized, + auth_url: self.auth_url.clone(), + expires_in_secs: self.expires_at.map(remaining_secs), + } + } +} + +fn remaining_secs(expires_at: Instant) -> u64 { + if expires_at <= Instant::now() { + 0 + } else { + expires_at.saturating_duration_since(Instant::now()).as_secs() + } } pub fn filter_perms(requested: &[String], allowed: &[String]) -> Vec<String> { @@ -258,6 +319,56 @@ mod tests { assert!(found_again.is_none()); } + #[test] + fn public_view_marks_inbound_local_signer_sessions() { + let session = build_session("inbound", None); + + let view = session.public_view(); + + assert_eq!(view.session_id, "inbound"); + assert_eq!(view.role, Nip46SessionRole::InboundLocalSigner); + assert_eq!(view.client_pubkey, session.client_pubkey.to_hex()); + assert_eq!(view.signer_pubkey, session.remote_signer_pubkey.to_hex()); + assert_eq!(view.permissions, session.perms); + } + + #[test] + fn public_view_marks_outbound_remote_signer_sessions() { + let client_keys = RadrootsNostrKeys::generate(); + let remote_signer_keys = RadrootsNostrKeys::generate(); + let session = Nip46Session { + id: "outbound".to_string(), + client: RadrootsNostrClient::new(client_keys.clone()), + client_keys: client_keys.clone(), + client_pubkey: client_keys.public_key(), + remote_signer_pubkey: remote_signer_keys.public_key(), + user_pubkey: None, + relays: vec!["wss://relay.example.com".to_string()], + perms: vec!["sign_event".to_string()], + name: Some("remote signer".to_string()), + url: Some("https://signer.example.com".to_string()), + image: None, + expires_at: Some(Instant::now() + Duration::from_secs(30)), + auth_required: true, + authorized: false, + auth_url: Some("https://signer.example.com/auth".to_string()), + pending_request: None, + }; + + let view = session.public_view(); + + assert_eq!(view.session_id, "outbound"); + assert_eq!(view.role, Nip46SessionRole::OutboundRemoteSigner); + assert_eq!(view.client_pubkey, session.client_pubkey.to_hex()); + assert_eq!(view.signer_pubkey, session.remote_signer_pubkey.to_hex()); + assert_eq!(view.relays, session.relays); + assert_eq!(view.permissions, session.perms); + assert!(view.auth_required); + assert!(!view.authorized); + assert_eq!(view.auth_url, session.auth_url); + assert!(view.expires_in_secs.is_some()); + } + #[tokio::test] async fn session_store_keeps_active() { let store = Nip46SessionStore::new(); diff --git a/src/transport/jsonrpc/methods/nip46/session_list.rs b/src/transport/jsonrpc/methods/nip46/session_list.rs @@ -1,55 +1,18 @@ -use std::time::{Instant}; - use anyhow::Result; use jsonrpsee::server::RpcModule; -use serde::Serialize; +use crate::core::nip46::session::Nip46SessionView; use crate::transport::jsonrpc::{MethodRegistry, RpcContext}; -#[derive(Clone, Serialize)] -struct Nip46SessionListEntry { - session_id: String, - client_pubkey: String, - remote_signer_pubkey: String, - user_pubkey: Option<String>, - relays: Vec<String>, - perms: Vec<String>, - name: Option<String>, - url: Option<String>, - image: Option<String>, - expires_in_secs: Option<u64>, -} - pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { registry.track("nip46.session.list"); m.register_async_method("nip46.session.list", |_params, ctx, _| async move { let sessions = ctx.state.nip46_sessions.list().await; let entries = sessions .into_iter() - .map(|session| Nip46SessionListEntry { - session_id: session.id, - client_pubkey: session.client_pubkey.to_hex(), - remote_signer_pubkey: session.remote_signer_pubkey.to_hex(), - user_pubkey: session.user_pubkey.map(|pubkey| pubkey.to_hex()), - relays: session.relays, - perms: session.perms, - name: session.name, - url: session.url, - image: session.image, - expires_in_secs: session - .expires_at - .map(|expires_at| remaining_secs(expires_at)), - }) + .map(|session| session.public_view()) .collect::<Vec<_>>(); - Ok::<Vec<Nip46SessionListEntry>, crate::transport::jsonrpc::RpcError>(entries) + Ok::<Vec<Nip46SessionView>, crate::transport::jsonrpc::RpcError>(entries) })?; Ok(()) } - -fn remaining_secs(expires_at: Instant) -> u64 { - if expires_at <= Instant::now() { - 0 - } else { - expires_at.saturating_duration_since(Instant::now()).as_secs() - } -} diff --git a/src/transport/jsonrpc/methods/nip46/session_status.rs b/src/transport/jsonrpc/methods/nip46/session_status.rs @@ -1,7 +1,8 @@ use anyhow::Result; use jsonrpsee::server::RpcModule; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use crate::core::nip46::session::Nip46SessionView; use crate::transport::jsonrpc::nip46::session; use crate::transport::jsonrpc::{MethodRegistry, RpcContext, RpcError}; @@ -10,19 +11,6 @@ struct Nip46SessionStatusParams { session_id: String, } -#[derive(Clone, Debug, Serialize)] -struct Nip46SessionStatusResponse { - session_id: String, - client_pubkey: String, - remote_signer_pubkey: String, - user_pubkey: Option<String>, - relays: Vec<String>, - perms: Vec<String>, - name: Option<String>, - url: Option<String>, - image: Option<String>, -} - pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Result<()> { registry.track("nip46.session.status"); m.register_async_method("nip46.session.status", |params, ctx, _| async move { @@ -30,17 +18,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res .parse() .map_err(|e| RpcError::InvalidParams(e.to_string()))?; let session = session::get_session(ctx.as_ref(), &session_id).await?; - Ok::<Nip46SessionStatusResponse, RpcError>(Nip46SessionStatusResponse { - session_id, - client_pubkey: session.client_pubkey.to_hex(), - remote_signer_pubkey: session.remote_signer_pubkey.to_hex(), - user_pubkey: session.user_pubkey.map(|pubkey| pubkey.to_hex()), - relays: session.relays, - perms: session.perms, - name: session.name, - url: session.url, - image: session.image, - }) + Ok::<Nip46SessionView, RpcError>(session.public_view()) })?; Ok(()) }