commit c32ca9a6f5d9a1fde25f23f59337ff37618bc628
parent 580ec6f4d69a55c59b478f3c5ac6d1774419c715
Author: triesap <tyson@radroots.org>
Date: Sun, 12 Apr 2026 19:20:43 +0000
myc: consume shared nip46 handler
Diffstat:
5 files changed, 164 insertions(+), 398 deletions(-)
diff --git a/src/audit_sqlite.rs b/src/audit_sqlite.rs
@@ -24,6 +24,7 @@ static MYC_OPERATION_AUDIT_MIGRATIONS: &[Migration] = &[Migration {
down_sql: include_str!("../migrations/0000_runtime_audit_init.down.sql"),
}];
+/// Myc keeps its operational audit store local to the service boundary.
pub struct MycSqliteOperationAuditStore {
db: MycOperationAuditSqliteDb,
config: MycAuditConfig,
diff --git a/src/control.rs b/src/control.rs
@@ -358,13 +358,10 @@ async fn replay_authorized_request(
));
}
};
- runtime
- .signer_context()
- .record_signer_request_audit(&evaluation.audit);
- let handled_request = match handler
+ let handled_outcome = match handler
.handle_authorized_request_evaluation(pending_request.request_message.clone(), evaluation)
{
- Ok(handled_request) => handled_request,
+ Ok(handled_outcome) => handled_outcome,
Err(error) => {
return Err(cancel_auth_replay_workflow_on_error(
runtime,
@@ -375,7 +372,11 @@ async fn replay_authorized_request(
));
}
};
- let Some((response, _, consume_connect_secret_for)) = handled_request.into_publish_parts()
+ if let Some(audit) = handled_outcome.audit.as_ref() {
+ runtime.signer_context().record_signer_request_audit(audit);
+ }
+ let Some((response, _, consume_connect_secret_for)) =
+ handled_outcome.handled_request.into_publish_parts()
else {
let error = MycError::InvalidOperation(
"authorized auth replay did not produce a response".to_owned(),
diff --git a/src/outbox_sqlite.rs b/src/outbox_sqlite.rs
@@ -22,6 +22,7 @@ static MYC_DELIVERY_OUTBOX_MIGRATIONS: &[Migration] = &[Migration {
down_sql: include_str!("../migrations/0000_delivery_outbox_init.down.sql"),
}];
+/// Myc keeps its delivery outbox store local to the service boundary.
pub struct MycSqliteDeliveryOutboxStore {
db: MycDeliveryOutboxSqliteDb,
}
diff --git a/src/policy.rs b/src/policy.rs
@@ -8,9 +8,9 @@ use radroots_nostr_connect::prelude::{
RadrootsNostrConnectRequest, RadrootsNostrConnectRequestMessage,
};
use radroots_nostr_signer::prelude::{
- RadrootsNostrSignerApprovalRequirement, RadrootsNostrSignerConnectionRecord,
- RadrootsNostrSignerManager, RadrootsNostrSignerRequestAuditRecord,
- RadrootsNostrSignerRequestDecision,
+ RadrootsNostrSignerApprovalRequirement, RadrootsNostrSignerBackend,
+ RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerManager,
+ RadrootsNostrSignerNip46ConnectDecision, RadrootsNostrSignerNip46Policy,
};
use crate::config::{MycConnectionApproval, MycPolicyConfig};
@@ -176,9 +176,9 @@ impl MycPolicyContext {
}
}
- pub fn prepare_request(
+ pub fn prepare_request<B: RadrootsNostrSignerBackend>(
&self,
- manager: &RadrootsNostrSignerManager,
+ backend: &B,
connection: &RadrootsNostrSignerConnectionRecord,
request_message: &RadrootsNostrConnectRequestMessage,
) -> Result<Option<String>, MycError> {
@@ -196,7 +196,7 @@ impl MycPolicyContext {
{
if self.request_uses_automatic_auth(connection, &request_message.request) {
if let Some(reason) =
- self.require_auth_challenge_with_guardrails(manager, connection)?
+ self.require_auth_challenge_with_guardrails(backend, connection)?
{
return Ok(Some(reason));
}
@@ -207,7 +207,7 @@ impl MycPolicyContext {
}
} else if self.should_require_fresh_auth(connection, &request_message.request) {
if let Some(reason) =
- self.require_auth_challenge_with_guardrails(manager, connection)?
+ self.require_auth_challenge_with_guardrails(backend, connection)?
{
return Ok(Some(reason));
}
@@ -240,29 +240,12 @@ impl MycPolicyContext {
if !self.stale_session_requires_cleanup(&connection) {
continue;
}
- self.require_auth_challenge(manager, &connection)?;
+ self.require_auth_challenge_with_manager(manager, &connection)?;
cleaned += 1;
}
Ok(cleaned)
}
- pub fn record_policy_denied_request(
- &self,
- manager: &RadrootsNostrSignerManager,
- connection: &RadrootsNostrSignerConnectionRecord,
- request_message: &RadrootsNostrConnectRequestMessage,
- reason: impl Into<String>,
- ) -> Result<RadrootsNostrSignerRequestAuditRecord, MycError> {
- let reason = reason.into();
- Ok(manager.record_request(
- &connection.connection_id,
- &request_message.id,
- request_message.request.method(),
- RadrootsNostrSignerRequestDecision::Denied,
- Some(reason.clone()),
- )?)
- }
-
fn client_is_denied(&self, client_public_key: &PublicKey) -> bool {
self.denied_client_pubkeys
.contains(&client_public_key.to_hex())
@@ -386,9 +369,9 @@ impl MycPolicyContext {
self.auth_url.is_some() && self.client_is_trusted(&connection.client_public_key)
}
- fn require_auth_challenge_with_guardrails(
+ fn require_auth_challenge_with_guardrails<B: RadrootsNostrSignerBackend>(
&self,
- manager: &RadrootsNostrSignerManager,
+ backend: &B,
connection: &RadrootsNostrSignerConnectionRecord,
) -> Result<Option<String>, MycError> {
if let Some(retry_after_secs) = self
@@ -401,11 +384,20 @@ impl MycPolicyContext {
retry_after_secs,
)));
}
- self.require_auth_challenge(manager, connection)?;
+ self.require_auth_challenge_with_backend(backend, connection)?;
Ok(None)
}
- fn require_auth_challenge(
+ fn require_auth_challenge_with_backend<B: RadrootsNostrSignerBackend>(
+ &self,
+ backend: &B,
+ connection: &RadrootsNostrSignerConnectionRecord,
+ ) -> Result<(), MycError> {
+ backend.require_auth_challenge(&connection.connection_id, self.auth_url()?)?;
+ Ok(())
+ }
+
+ fn require_auth_challenge_with_manager(
&self,
manager: &RadrootsNostrSignerManager,
connection: &RadrootsNostrSignerConnectionRecord,
@@ -448,6 +440,56 @@ impl MycPolicyContext {
}
}
+impl<B: RadrootsNostrSignerBackend> RadrootsNostrSignerNip46Policy<B> for MycPolicyContext {
+ fn connect_decision(
+ &self,
+ client_public_key: &PublicKey,
+ ) -> RadrootsNostrSignerNip46ConnectDecision {
+ match self.connect_decision(client_public_key) {
+ MycConnectDecision::Allow => RadrootsNostrSignerNip46ConnectDecision::Allow,
+ MycConnectDecision::RequireApproval => {
+ RadrootsNostrSignerNip46ConnectDecision::RequireApproval
+ }
+ MycConnectDecision::Deny => RadrootsNostrSignerNip46ConnectDecision::Deny,
+ }
+ }
+
+ fn connect_rate_limit_denied_reason(&self, client_public_key: &PublicKey) -> Option<String> {
+ self.connect_rate_limit_denied_reason(client_public_key)
+ }
+
+ fn approval_requirement_for_client(
+ &self,
+ client_public_key: &PublicKey,
+ ) -> Option<RadrootsNostrSignerApprovalRequirement> {
+ self.approval_requirement_for_client(client_public_key)
+ }
+
+ fn filtered_requested_permissions(
+ &self,
+ requested_permissions: &RadrootsNostrConnectPermissions,
+ ) -> RadrootsNostrConnectPermissions {
+ self.filtered_requested_permissions(requested_permissions)
+ }
+
+ fn auto_granted_permissions(
+ &self,
+ requested_permissions: &RadrootsNostrConnectPermissions,
+ ) -> RadrootsNostrConnectPermissions {
+ self.auto_granted_permissions(requested_permissions)
+ }
+
+ fn prepare_request(
+ &self,
+ backend: &B,
+ connection: &RadrootsNostrSignerConnectionRecord,
+ request_message: &RadrootsNostrConnectRequestMessage,
+ ) -> Result<Option<String>, radroots_nostr_signer::prelude::RadrootsNostrSignerError> {
+ self.prepare_request(backend, connection, request_message)
+ .map_err(myc_policy_signer_error)
+ }
+}
+
impl MycPolicyRateLimiter {
fn check_and_record(&self, key: &str) -> Option<u64> {
let now_unix = now_unix_secs();
@@ -625,6 +667,12 @@ fn now_unix_secs() -> u64 {
.unwrap_or_default()
}
+fn myc_policy_signer_error(
+ error: MycError,
+) -> radroots_nostr_signer::prelude::RadrootsNostrSignerError {
+ radroots_nostr_signer::prelude::RadrootsNostrSignerError::InvalidState(error.to_string())
+}
+
#[cfg(test)]
mod tests {
use super::{MycConnectDecision, MycPolicyContext};
@@ -637,8 +685,9 @@ mod tests {
RadrootsNostrConnectRequestMessage,
};
use radroots_nostr_signer::prelude::{
- RadrootsNostrSignerApprovalRequirement, RadrootsNostrSignerAuthState,
- RadrootsNostrSignerConnectionDraft, RadrootsNostrSignerManager,
+ RadrootsNostrEmbeddedSignerBackend, RadrootsNostrSignerApprovalRequirement,
+ RadrootsNostrSignerAuthState, RadrootsNostrSignerConnectionDraft,
+ RadrootsNostrSignerManager,
};
use serde_json::json;
use std::thread;
@@ -663,6 +712,14 @@ mod tests {
manager
}
+ fn backend_for(manager: &RadrootsNostrSignerManager) -> RadrootsNostrEmbeddedSignerBackend {
+ RadrootsNostrEmbeddedSignerBackend::new(
+ manager.clone(),
+ identity("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+ )
+ .expect("backend")
+ }
+
fn register_connection(
manager: &RadrootsNostrSignerManager,
client_public_key: PublicKey,
@@ -761,6 +818,7 @@ mod tests {
config.allowed_sign_event_kinds = vec![1];
let policy = MycPolicyContext::from_config(&config).expect("policy");
let manager = in_memory_manager();
+ let backend = backend_for(&manager);
let connection = register_connection(
&manager,
public_key("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
@@ -768,7 +826,7 @@ mod tests {
let denied = policy
.prepare_request(
- &manager,
+ &backend,
&connection,
&RadrootsNostrConnectRequestMessage::new(
"request-1",
@@ -817,6 +875,7 @@ mod tests {
config.auth_authorized_ttl_secs = Some(1);
let policy = MycPolicyContext::from_config(&config).expect("policy");
let manager = in_memory_manager();
+ let backend = backend_for(&manager);
let connection = register_connection(&manager, client_public_key);
manager
@@ -833,7 +892,7 @@ mod tests {
.expect("connection");
let denied = policy
.prepare_request(
- &manager,
+ &backend,
&connection,
&RadrootsNostrConnectRequestMessage::new(
"request-1",
@@ -870,6 +929,7 @@ mod tests {
config.reauth_after_inactivity_secs = Some(1);
let policy = MycPolicyContext::from_config(&config).expect("policy");
let manager = in_memory_manager();
+ let backend = backend_for(&manager);
let connection = register_connection(&manager, client_public_key);
manager
@@ -895,7 +955,7 @@ mod tests {
.expect("connection");
let denied = policy
.prepare_request(
- &manager,
+ &backend,
&connection,
&RadrootsNostrConnectRequestMessage::new(
"request-1",
diff --git a/src/transport/nip46.rs b/src/transport/nip46.rs
@@ -6,30 +6,36 @@ use radroots_nostr::prelude::{
RadrootsNostrRelayPoolNotification, RadrootsNostrRelayUrl,
};
use radroots_nostr_connect::prelude::{
- RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectRequest,
- RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse,
+ RADROOTS_NOSTR_CONNECT_RPC_KIND, RadrootsNostrConnectRequestMessage,
+ RadrootsNostrConnectResponse,
};
use radroots_nostr_signer::prelude::{
- RadrootsNostrSignerConnectEvaluation, RadrootsNostrSignerConnectionId,
- RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerHandledRequest,
- RadrootsNostrSignerNip46Codec, RadrootsNostrSignerNip46Signer,
- RadrootsNostrSignerRequestAction, RadrootsNostrSignerRequestEvaluation,
- RadrootsNostrSignerSessionLookup, RadrootsNostrSignerWorkflowId, connect_response_outcome,
- handled_request_for_action, response_from_hint,
+ RadrootsNostrSignerConnectionId, RadrootsNostrSignerHandledRequestOutcome,
+ RadrootsNostrSignerNip46Handler, RadrootsNostrSignerNip46Signer,
+ RadrootsNostrSignerRequestEvaluation, RadrootsNostrSignerWorkflowId,
};
use tokio::sync::broadcast;
+#[cfg(test)]
+use radroots_nostr_signer::prelude::RadrootsNostrSignerHandledRequest;
+
use crate::app::MycSignerContext;
+use crate::app::backend::MycSignerBackend;
use crate::audit::{MycOperationAuditKind, MycOperationAuditOutcome, MycOperationAuditRecord};
use crate::error::MycError;
use crate::outbox::{MycDeliveryOutboxKind, MycDeliveryOutboxRecord, MycDeliveryOutboxStore};
use crate::transport::MycNostrTransport;
+type MycNip46CoreHandler = RadrootsNostrSignerNip46Handler<
+ MycSignerBackend,
+ crate::policy::MycPolicyContext,
+ MycNip46Signer,
+>;
+
#[derive(Clone)]
pub struct MycNip46Handler {
signer: MycSignerContext,
- relays: Vec<RadrootsNostrRelayUrl>,
- codec: RadrootsNostrSignerNip46Codec<MycNip46Signer>,
+ handler: MycNip46CoreHandler,
}
pub struct MycNip46Service {
@@ -38,12 +44,7 @@ pub struct MycNip46Service {
delivery_outbox_store: Arc<dyn MycDeliveryOutboxStore>,
}
-enum MycPreparedRequestEvaluation {
- Denied(String),
- Evaluation(RadrootsNostrSignerRequestEvaluation),
-}
-
-type MycNip46HandledRequest = RadrootsNostrSignerHandledRequest;
+type MycNip46HandledOutcome = RadrootsNostrSignerHandledRequestOutcome;
#[derive(Clone)]
struct MycNip46Signer {
@@ -81,8 +82,8 @@ impl RadrootsNostrSignerNip46Signer for MycNip46Signer {
})
}
- fn user_public_key(&self) -> RadrootsNostrPublicKey {
- self.signer.user_identity().public_key()
+ fn user_identity(&self) -> radroots_identity::RadrootsIdentityPublic {
+ self.signer.user_public_identity()
}
fn sign_user_event(
@@ -152,25 +153,26 @@ impl RadrootsNostrSignerNip46Signer for MycNip46Signer {
impl MycNip46Handler {
pub fn new(signer: MycSignerContext, relays: Vec<RadrootsNostrRelayUrl>) -> Self {
- let codec = RadrootsNostrSignerNip46Codec::new(MycNip46Signer {
- signer: signer.clone(),
- });
- Self {
- signer,
+ let handler = RadrootsNostrSignerNip46Handler::new(
+ MycSignerBackend::new(signer.clone()),
+ signer.policy().clone(),
relays,
- codec,
- }
+ MycNip46Signer {
+ signer: signer.clone(),
+ },
+ );
+ Self { signer, handler }
}
pub fn filter(&self) -> Result<RadrootsNostrFilter, MycError> {
- self.codec.filter().map_err(Into::into)
+ self.handler.filter().map_err(Into::into)
}
pub fn parse_request_event(
&self,
event: &RadrootsNostrEvent,
) -> Result<RadrootsNostrConnectRequestMessage, MycError> {
- self.codec.parse_request_event(event).map_err(Into::into)
+ self.handler.parse_request_event(event).map_err(Into::into)
}
pub fn build_response_event(
@@ -179,7 +181,7 @@ impl MycNip46Handler {
request_id: impl Into<String>,
response: RadrootsNostrConnectResponse,
) -> Result<radroots_nostr::prelude::RadrootsNostrEventBuilder, MycError> {
- self.codec
+ self.handler
.build_response_event(client_public_key, request_id, response)
.map_err(Into::into)
}
@@ -188,36 +190,10 @@ impl MycNip46Handler {
&self,
client_public_key: RadrootsNostrPublicKey,
request_message: RadrootsNostrConnectRequestMessage,
- ) -> Result<MycNip46HandledRequest, MycError> {
- match request_message.request.clone() {
- RadrootsNostrConnectRequest::Connect { secret, .. } => {
- self.handle_connect_request(client_public_key, request_message.request, secret)
- }
- RadrootsNostrConnectRequest::SignEvent(unsigned_event) => {
- self.handle_sign_event_request(client_public_key, request_message, unsigned_event)
- }
- RadrootsNostrConnectRequest::Nip04Encrypt { .. }
- | RadrootsNostrConnectRequest::Nip04Decrypt { .. }
- | RadrootsNostrConnectRequest::Nip44Encrypt { .. }
- | RadrootsNostrConnectRequest::Nip44Decrypt { .. } => {
- self.handle_crypto_request(client_public_key, request_message)
- }
- RadrootsNostrConnectRequest::GetPublicKey
- | RadrootsNostrConnectRequest::GetSessionCapability
- | RadrootsNostrConnectRequest::Ping
- | RadrootsNostrConnectRequest::SwitchRelays => {
- self.handle_base_request(client_public_key, request_message)
- }
- _ => Ok(MycNip46HandledRequest::respond(
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: format!(
- "method `{}` is not implemented yet",
- request_message.request.method()
- ),
- },
- )),
- }
+ ) -> Result<MycNip46HandledOutcome, MycError> {
+ self.handler
+ .handle_request(client_public_key, request_message)
+ .map_err(Into::into)
}
#[cfg(test)]
@@ -227,308 +203,27 @@ impl MycNip46Handler {
request_message: RadrootsNostrConnectRequestMessage,
) -> Result<RadrootsNostrConnectResponse, MycError> {
match self.handle_request(client_public_key, request_message)? {
- MycNip46HandledRequest::Respond { response, .. } => Ok(response),
- MycNip46HandledRequest::Ignore => Err(MycError::InvalidOperation(
+ MycNip46HandledOutcome {
+ handled_request: RadrootsNostrSignerHandledRequest::Respond { response, .. },
+ ..
+ } => Ok(response),
+ MycNip46HandledOutcome {
+ handled_request: RadrootsNostrSignerHandledRequest::Ignore,
+ ..
+ } => Err(MycError::InvalidOperation(
"request was ignored without a response".to_owned(),
)),
}
}
- fn handle_connect_request(
- &self,
- client_public_key: RadrootsNostrPublicKey,
- request: RadrootsNostrConnectRequest,
- secret: Option<String>,
- ) -> Result<MycNip46HandledRequest, MycError> {
- let manager = self.signer.load_signer_manager()?;
- let connect_decision = self.signer.policy().connect_decision(&client_public_key);
- if let Some(connect_secret) = secret.as_deref() {
- if let Some(connection) = manager.find_connection_by_connect_secret(connect_secret)? {
- if connection.connect_secret_is_consumed() {
- tracing::debug!(
- connection_id = %connection.connection_id,
- "ignoring reused consumed NIP-46 connect secret"
- );
- return Ok(MycNip46HandledRequest::Ignore);
- }
- }
- }
- if !matches!(connect_decision, crate::policy::MycConnectDecision::Deny) {
- if let Some(reason) = self
- .signer
- .policy()
- .connect_rate_limit_denied_reason(&client_public_key)
- {
- return Ok(MycNip46HandledRequest::respond(
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: reason,
- },
- ));
- }
- }
- let evaluation = manager.evaluate_connect_request(client_public_key, request)?;
-
- match evaluation {
- RadrootsNostrSignerConnectEvaluation::ExistingConnection(connection) => {
- if secret.is_some() && connection.connect_secret_is_consumed() {
- tracing::debug!(
- connection_id = %connection.connection_id,
- "ignoring reused consumed NIP-46 connect secret"
- );
- return Ok(MycNip46HandledRequest::Ignore);
- }
- if matches!(connect_decision, crate::policy::MycConnectDecision::Deny) {
- return Ok(MycNip46HandledRequest::respond(
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: "client public key denied by policy".to_owned(),
- },
- ));
- }
- Ok(connect_response_outcome(&connection, secret))
- }
- RadrootsNostrSignerConnectEvaluation::RegistrationRequired(proposal) => {
- let requested_permissions = self
- .signer
- .policy()
- .filtered_requested_permissions(&proposal.requested_permissions);
- let Some(approval_requirement) = self
- .signer
- .policy()
- .approval_requirement_for_client(&client_public_key)
- else {
- return Ok(MycNip46HandledRequest::respond(
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: "client public key denied by policy".to_owned(),
- },
- ));
- };
- let draft = proposal
- .into_connection_draft(self.signer.user_public_identity())
- .with_requested_permissions(requested_permissions)
- .with_relays(self.relays.clone())
- .with_approval_requirement(approval_requirement);
- let connection = manager.register_connection(draft)?;
- if approval_requirement
- == radroots_nostr_signer::prelude::RadrootsNostrSignerApprovalRequirement::NotRequired
- {
- let granted_permissions = self
- .signer
- .policy()
- .auto_granted_permissions(&connection.requested_permissions);
- let _ = manager.set_granted_permissions(
- &connection.connection_id,
- granted_permissions,
- )?;
- }
- Ok(connect_response_outcome(&connection, secret))
- }
- }
- }
-
- fn handle_base_request(
- &self,
- client_public_key: RadrootsNostrPublicKey,
- request_message: RadrootsNostrConnectRequestMessage,
- ) -> Result<MycNip46HandledRequest, MycError> {
- let connection = match self.lookup_connection(client_public_key)? {
- Ok(connection) => connection,
- Err(response) => return Ok(MycNip46HandledRequest::respond(response)),
- };
-
- match self.evaluate_request_with_policy(&connection, request_message)? {
- MycPreparedRequestEvaluation::Denied(reason) => {
- Ok(MycNip46HandledRequest::respond_for_connection(
- Some(connection.connection_id.clone()),
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: reason,
- },
- ))
- }
- MycPreparedRequestEvaluation::Evaluation(evaluation) => {
- let response_hint = match &evaluation.action {
- RadrootsNostrSignerRequestAction::Allowed { response_hint, .. } => {
- Some(response_hint.clone())
- }
- _ => None,
- };
- Ok(handled_request_for_action(
- &evaluation.connection,
- evaluation.action,
- || {
- Ok(response_from_hint(
- &evaluation.connection,
- response_hint.expect("allowed action carries response hint"),
- ))
- },
- )?)
- }
- }
- }
-
- fn handle_sign_event_request(
- &self,
- client_public_key: RadrootsNostrPublicKey,
- request_message: RadrootsNostrConnectRequestMessage,
- unsigned_event: nostr::UnsignedEvent,
- ) -> Result<MycNip46HandledRequest, MycError> {
- let connection = match self.lookup_connection(client_public_key)? {
- Ok(connection) => connection,
- Err(response) => return Ok(MycNip46HandledRequest::respond(response)),
- };
-
- match self.evaluate_request_with_policy(&connection, request_message)? {
- MycPreparedRequestEvaluation::Denied(reason) => {
- Ok(MycNip46HandledRequest::respond_for_connection(
- Some(connection.connection_id.clone()),
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: reason,
- },
- ))
- }
- MycPreparedRequestEvaluation::Evaluation(evaluation) => Ok(handled_request_for_action(
- &evaluation.connection,
- evaluation.action,
- || self.codec.sign_event_response(unsigned_event),
- )?),
- }
- }
-
- fn handle_crypto_request(
- &self,
- client_public_key: RadrootsNostrPublicKey,
- request_message: RadrootsNostrConnectRequestMessage,
- ) -> Result<MycNip46HandledRequest, MycError> {
- let request = request_message.request.clone();
- let connection = match self.lookup_connection(client_public_key)? {
- Ok(connection) => connection,
- Err(response) => return Ok(MycNip46HandledRequest::respond(response)),
- };
-
- match self.evaluate_request_with_policy(&connection, request_message)? {
- MycPreparedRequestEvaluation::Denied(reason) => {
- Ok(MycNip46HandledRequest::respond_for_connection(
- Some(connection.connection_id.clone()),
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: reason,
- },
- ))
- }
- MycPreparedRequestEvaluation::Evaluation(evaluation) => Ok(handled_request_for_action(
- &evaluation.connection,
- evaluation.action,
- || self.codec.crypto_response(request),
- )?),
- }
- }
-
pub(crate) fn handle_authorized_request_evaluation(
&self,
request_message: RadrootsNostrConnectRequestMessage,
evaluation: RadrootsNostrSignerRequestEvaluation,
- ) -> Result<MycNip46HandledRequest, MycError> {
- Ok(match request_message.request.clone() {
- RadrootsNostrConnectRequest::SignEvent(unsigned_event) => {
- handled_request_for_action(&evaluation.connection, evaluation.action, || {
- self.codec.sign_event_response(unsigned_event)
- })?
- }
- RadrootsNostrConnectRequest::Nip04Encrypt { .. }
- | RadrootsNostrConnectRequest::Nip04Decrypt { .. }
- | RadrootsNostrConnectRequest::Nip44Encrypt { .. }
- | RadrootsNostrConnectRequest::Nip44Decrypt { .. } => {
- handled_request_for_action(&evaluation.connection, evaluation.action, || {
- self.codec.crypto_response(request_message.request)
- })?
- }
- RadrootsNostrConnectRequest::GetPublicKey
- | RadrootsNostrConnectRequest::GetSessionCapability
- | RadrootsNostrConnectRequest::Ping
- | RadrootsNostrConnectRequest::SwitchRelays => {
- let response_hint = match &evaluation.action {
- RadrootsNostrSignerRequestAction::Allowed { response_hint, .. } => {
- Some(response_hint.clone())
- }
- _ => None,
- };
- handled_request_for_action(&evaluation.connection, evaluation.action, || {
- Ok(response_from_hint(
- &evaluation.connection,
- response_hint.expect("allowed action carries response hint"),
- ))
- })?
- }
- other => MycNip46HandledRequest::respond_for_connection(
- Some(evaluation.connection.connection_id.clone()),
- RadrootsNostrConnectResponse::Error {
- result: None,
- error: format!("method `{}` is not implemented yet", other.method()),
- },
- ),
- })
- }
-
- fn evaluate_request_with_policy(
- &self,
- connection: &RadrootsNostrSignerConnectionRecord,
- request_message: RadrootsNostrConnectRequestMessage,
- ) -> Result<MycPreparedRequestEvaluation, MycError> {
- let manager = self.signer.load_signer_manager()?;
- if let Some(reason) =
- self.signer
- .policy()
- .prepare_request(&manager, connection, &request_message)?
- {
- let audit = self.signer.policy().record_policy_denied_request(
- &manager,
- connection,
- &request_message,
- reason,
- )?;
- self.signer.record_signer_request_audit(&audit);
- return Ok(MycPreparedRequestEvaluation::Denied(
- audit
- .message
- .unwrap_or_else(|| "request denied by policy".to_owned()),
- ));
- }
-
- let evaluation = manager.evaluate_request(&connection.connection_id, request_message)?;
- self.signer.record_signer_request_audit(&evaluation.audit);
- Ok(MycPreparedRequestEvaluation::Evaluation(evaluation))
- }
-
- fn lookup_connection(
- &self,
- client_public_key: RadrootsNostrPublicKey,
- ) -> Result<Result<RadrootsNostrSignerConnectionRecord, RadrootsNostrConnectResponse>, MycError>
- {
- Ok(
- match self
- .signer
- .load_signer_manager()?
- .lookup_session(&client_public_key, None)?
- {
- RadrootsNostrSignerSessionLookup::Connection(connection) => Ok(connection),
- RadrootsNostrSignerSessionLookup::None => {
- Err(RadrootsNostrConnectResponse::Error {
- result: None,
- error: "unauthorized".to_owned(),
- })
- }
- RadrootsNostrSignerSessionLookup::Ambiguous(_) => {
- Err(RadrootsNostrConnectResponse::Error {
- result: None,
- error: "ambiguous client sessions".to_owned(),
- })
- }
- },
- )
+ ) -> Result<MycNip46HandledOutcome, MycError> {
+ self.handler
+ .handle_authorized_request_evaluation(request_message, evaluation)
+ .map_err(Into::into)
}
}
@@ -596,18 +291,21 @@ impl MycNip46Service {
};
let request_id = request_message.id.clone();
- let handled_request = match self.handler.handle_request(event.pubkey, request_message) {
- Ok(handled_request) => handled_request,
+ let handled_outcome = match self.handler.handle_request(event.pubkey, request_message) {
+ Ok(handled_outcome) => handled_outcome,
Err(error) => {
tracing::warn!(error = %error, "failed to handle NIP-46 request");
- MycNip46HandledRequest::respond(RadrootsNostrConnectResponse::Error {
+ MycNip46HandledOutcome::respond(RadrootsNostrConnectResponse::Error {
result: None,
error: error.to_string(),
})
}
};
+ if let Some(audit) = handled_outcome.audit.as_ref() {
+ self.handler.signer.record_signer_request_audit(audit);
+ }
let Some((response, connection_id, consume_connect_secret_for)) =
- handled_request.into_publish_parts()
+ handled_outcome.handled_request.into_publish_parts()
else {
tracing::debug!(
request_id = %request_id,
@@ -984,13 +682,15 @@ mod tests {
RadrootsNostrConnectRequestMessage, RadrootsNostrConnectResponse,
RadrootsNostrConnectResponseEnvelope,
};
- use radroots_nostr_signer::prelude::RadrootsNostrSignerConnectionRecord;
+ use radroots_nostr_signer::prelude::{
+ RadrootsNostrSignerConnectionRecord, RadrootsNostrSignerHandledRequest,
+ };
use serde_json::json;
use crate::app::MycRuntime;
use crate::config::{MycConfig, MycConnectionApproval};
- use super::{MycNip46HandledRequest, MycNip46Handler};
+ use super::MycNip46Handler;
fn write_identity(path: &std::path::Path, secret_key: &str) {
let identity =
@@ -1336,7 +1036,10 @@ mod tests {
)
.expect("ignored response");
- assert_eq!(ignored, MycNip46HandledRequest::Ignore);
+ assert_eq!(
+ ignored.handled_request,
+ RadrootsNostrSignerHandledRequest::Ignore
+ );
let connections = runtime
.signer_manager()
.expect("manager")