commit e42b6344a3c97ad363bb49718e807e6d01736317
parent 2ccd369dd5073e1e11f46faa78378e8c08eee067
Author: triesap <tyson@radroots.org>
Date: Fri, 10 Apr 2026 21:00:59 +0000
signer: carry myc authority into daemon writes
Diffstat:
6 files changed, 355 insertions(+), 41 deletions(-)
diff --git a/src/runtime/daemon.rs b/src/runtime/daemon.rs
@@ -13,6 +13,7 @@ use crate::domain::runtime::{
};
use crate::runtime::config::RuntimeConfig;
use crate::runtime::provider;
+use crate::runtime::signer::ActorWriteSignerAuthority;
const RPC_SOURCE: &str = "daemon rpc · durable write plane";
const BRIDGE_SOURCE: &str = "daemon bridge · durable write plane";
@@ -126,6 +127,8 @@ struct Nip46SessionRemote {
auth_required: bool,
authorized: bool,
expires_in_secs: Option<u64>,
+ #[serde(default)]
+ signer_authority: Option<ActorWriteSignerAuthority>,
}
#[derive(Debug, Clone, Deserialize)]
@@ -386,6 +389,7 @@ pub fn bridge_listing_publish(
kind: u32,
idempotency_key: Option<&str>,
signer_session_id: Option<&str>,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
) -> Result<BridgeListingPublishResult, DaemonRpcError> {
let target = actor_write_target(config)?;
let response: BridgePublishResponseRemote = call(
@@ -396,6 +400,7 @@ pub fn bridge_listing_publish(
"kind": kind,
"idempotency_key": idempotency_key,
"signer_session_id": signer_session_id,
+ "signer_authority": signer_authority,
})),
RpcAuthMode::BridgeBearer,
)?;
@@ -417,6 +422,7 @@ pub fn bridge_order_request(
order: &RadrootsTradeOrder,
idempotency_key: Option<&str>,
signer_session_id: Option<&str>,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
) -> Result<BridgeOrderRequestResult, DaemonRpcError> {
let target = actor_write_target(config)?;
let response: BridgePublishResponseRemote = call(
@@ -426,6 +432,7 @@ pub fn bridge_order_request(
"order": order,
"idempotency_key": idempotency_key,
"signer_session_id": signer_session_id,
+ "signer_authority": signer_authority,
})),
RpcAuthMode::BridgeBearer,
)?;
@@ -503,6 +510,7 @@ pub fn resolve_signer_session_id(
actor_pubkey: &str,
event_kind: u32,
requested_session_id: Option<&str>,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
) -> Result<String, DaemonRpcError> {
let target = actor_write_target(config)?;
let sessions = nip46_sessions_with_target(&target)?;
@@ -516,13 +524,21 @@ pub fn resolve_signer_session_id(
"requested signer session `{session_id}` was not found"
)));
};
- validate_signer_session(&session, actor_role, actor_pubkey, event_kind)?;
+ validate_signer_session(
+ &session,
+ actor_role,
+ actor_pubkey,
+ event_kind,
+ signer_authority,
+ )?;
return Ok(session.session_id);
}
let mut matches = sessions
.into_iter()
- .filter(|session| session_matches_actor(session, actor_pubkey, event_kind))
+ .filter(|session| {
+ session_matches_actor(session, actor_pubkey, event_kind, signer_authority)
+ })
.map(|session| session.session_id)
.collect::<Vec<_>>();
@@ -542,6 +558,7 @@ fn validate_signer_session(
actor_role: &str,
actor_pubkey: &str,
event_kind: u32,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
) -> Result<(), DaemonRpcError> {
if !session.authorized {
return Err(DaemonRpcError::Unconfigured(format!(
@@ -561,6 +578,7 @@ fn validate_signer_session(
session.session_id
)));
}
+ validate_signer_authority(session, signer_authority)?;
Ok(())
}
@@ -568,10 +586,61 @@ fn session_matches_actor(
session: &Nip46SessionRemote,
actor_pubkey: &str,
event_kind: u32,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
) -> bool {
session.authorized
&& session.signer_pubkey.eq_ignore_ascii_case(actor_pubkey)
&& sign_event_allowed(&session.permissions, event_kind)
+ && signer_authority_matches(session, signer_authority)
+}
+
+fn validate_signer_authority(
+ session: &Nip46SessionRemote,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
+) -> Result<(), DaemonRpcError> {
+ let Some(expected) = signer_authority else {
+ return Ok(());
+ };
+ let Some(actual) = session.signer_authority.as_ref() else {
+ return Err(DaemonRpcError::Unconfigured(format!(
+ "requested signer session `{}` is missing signer authority continuity metadata",
+ session.session_id
+ )));
+ };
+ if actual.provider_runtime_id != expected.provider_runtime_id {
+ return Err(DaemonRpcError::Unconfigured(format!(
+ "requested signer session `{}` provider `{}` does not match required provider `{}`",
+ session.session_id, actual.provider_runtime_id, expected.provider_runtime_id
+ )));
+ }
+ if actual.account_identity_id != expected.account_identity_id {
+ return Err(DaemonRpcError::Unconfigured(format!(
+ "requested signer session `{}` account identity `{}` does not match required account `{}`",
+ session.session_id, actual.account_identity_id, expected.account_identity_id
+ )));
+ }
+ if actual.provider_signer_session_id != expected.provider_signer_session_id {
+ return Err(DaemonRpcError::Unconfigured(format!(
+ "requested signer session `{}` provider signer session `{}` does not match required provider session `{}`",
+ session.session_id,
+ actual
+ .provider_signer_session_id
+ .as_deref()
+ .unwrap_or("<none>"),
+ expected
+ .provider_signer_session_id
+ .as_deref()
+ .unwrap_or("<none>")
+ )));
+ }
+ Ok(())
+}
+
+fn signer_authority_matches(
+ session: &Nip46SessionRemote,
+ signer_authority: Option<&ActorWriteSignerAuthority>,
+) -> bool {
+ validate_signer_authority(session, signer_authority).is_ok()
}
fn sign_event_allowed(perms: &[String], kind: u32) -> bool {
diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs
@@ -33,7 +33,7 @@ use crate::runtime::accounts;
use crate::runtime::config::RuntimeConfig;
use crate::runtime::daemon;
use crate::runtime::daemon::DaemonRpcError;
-use crate::runtime::signer::{ActorWriteBindingError, validate_actor_write_binding};
+use crate::runtime::signer::{ActorWriteBindingError, resolve_actor_write_authority};
use crate::runtime::sync::freshness_from_executor;
const DRAFT_KIND: &str = "listing_draft_v1";
@@ -544,19 +544,21 @@ fn mutate(
});
}
- if let Err(error) =
- validate_actor_write_binding(config, "seller", canonical.seller_pubkey.as_str())
- {
- return Ok(binding_error_view(
- config,
- args,
- operation,
- &canonical,
- listing_addr,
- event_preview,
- error,
- ));
- }
+ let signer_authority =
+ match resolve_actor_write_authority(config, "seller", canonical.seller_pubkey.as_str()) {
+ Ok(authority) => authority,
+ Err(error) => {
+ return Ok(binding_error_view(
+ config,
+ args,
+ operation,
+ &canonical,
+ listing_addr,
+ event_preview,
+ error,
+ ));
+ }
+ };
let signer_session_id = match daemon::resolve_signer_session_id(
config,
@@ -564,6 +566,7 @@ fn mutate(
canonical.seller_pubkey.as_str(),
KIND_LISTING,
args.signer_session_id.as_deref(),
+ signer_authority.as_ref(),
) {
Ok(session_id) => session_id,
Err(error) => {
@@ -585,6 +588,7 @@ fn mutate(
KIND_LISTING,
args.idempotency_key.as_deref(),
Some(signer_session_id.as_str()),
+ signer_authority.as_ref(),
) {
Ok(result) => {
let failed = result.status == "failed";
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -22,7 +22,7 @@ use crate::runtime::RuntimeError;
use crate::runtime::accounts;
use crate::runtime::config::RuntimeConfig;
use crate::runtime::daemon::{self, DaemonRpcError};
-use crate::runtime::signer::{ActorWriteBindingError, validate_actor_write_binding};
+use crate::runtime::signer::{ActorWriteBindingError, resolve_actor_write_authority};
const ORDER_DRAFT_KIND: &str = "order_draft_v1";
const ORDER_SOURCE: &str = "local order drafts · local first";
@@ -419,11 +419,11 @@ pub fn submit(
});
}
- if let Err(error) =
- validate_actor_write_binding(config, "buyer", loaded.document.order.buyer_pubkey.as_str())
- {
- return Ok(order_binding_error_view(config, &loaded, args, error));
- }
+ let signer_authority =
+ match resolve_actor_write_authority(config, "buyer", loaded.document.order.buyer_pubkey.as_str()) {
+ Ok(authority) => authority,
+ Err(error) => return Ok(order_binding_error_view(config, &loaded, args, error)),
+ };
let signer_session_id = match daemon::resolve_signer_session_id(
config,
@@ -431,6 +431,7 @@ pub fn submit(
loaded.document.order.buyer_pubkey.as_str(),
u32::from(RadrootsTradeMessageType::OrderRequest.kind()),
args.signer_session_id.as_deref(),
+ signer_authority.as_ref(),
) {
Ok(session_id) => session_id,
Err(error) => return Ok(order_submit_error_view(&loaded, args, error)),
@@ -442,6 +443,7 @@ pub fn submit(
&order,
args.idempotency_key.as_deref(),
Some(signer_session_id.as_str()),
+ signer_authority.as_ref(),
) {
Ok(result) => {
let mut updated = loaded.document.clone();
diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs
@@ -12,6 +12,7 @@ use radroots_nostr_signer::prelude::{
RadrootsNostrLocalSignerAvailability, RadrootsNostrLocalSignerCapability,
RadrootsNostrSignerCapability,
};
+use serde::{Deserialize, Serialize};
const SIGNER_BINDING_PROVIDER_RUNTIME_ID: &str = "myc";
const SIGNER_BINDING_MODEL: &str = "session_authorized_remote_signer";
@@ -29,6 +30,14 @@ pub enum ActorWriteBindingError {
Unavailable(String),
}
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct ActorWriteSignerAuthority {
+ pub provider_runtime_id: String,
+ pub account_identity_id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub provider_signer_session_id: Option<String>,
+}
+
pub fn resolve_signer_status(config: &RuntimeConfig) -> SignerStatusView {
match config.signer.backend {
SignerBackend::Local => resolve_local_signer_status(config),
@@ -36,13 +45,13 @@ pub fn resolve_signer_status(config: &RuntimeConfig) -> SignerStatusView {
}
}
-pub fn validate_actor_write_binding(
+pub fn resolve_actor_write_authority(
config: &RuntimeConfig,
actor_role: &str,
actor_pubkey: &str,
-) -> Result<(), ActorWriteBindingError> {
+) -> Result<Option<ActorWriteSignerAuthority>, ActorWriteBindingError> {
if !matches!(config.signer.backend, SignerBackend::Myc) {
- return Ok(());
+ return Ok(None);
}
let myc = crate::runtime::myc::resolve_status(&config.myc);
@@ -77,7 +86,17 @@ pub fn validate_actor_write_binding(
)));
}
- Ok(())
+ let Some(resolved_account_id) = resolution.resolved_account_id else {
+ return Err(ActorWriteBindingError::Unconfigured(
+ "myc signer binding reported ready without a resolved account identity".to_owned(),
+ ));
+ };
+
+ Ok(Some(ActorWriteSignerAuthority {
+ provider_runtime_id: SIGNER_BINDING_PROVIDER_RUNTIME_ID.to_owned(),
+ account_identity_id: resolved_account_id,
+ provider_signer_session_id: resolution.view.resolved_signer_session_id.clone(),
+ }))
}
fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
diff --git a/tests/listing.rs b/tests/listing.rs
@@ -355,6 +355,10 @@ fn listing_publish_and_update_use_durable_bridge_publish() {
assert!(account_output.status.success());
let account_json: Value =
serde_json::from_slice(account_output.stdout.as_slice()).expect("account json");
+ let account_id = account_json["account"]["id"]
+ .as_str()
+ .expect("account id")
+ .to_owned();
let seller_pubkey = account_json["public_identity"]["public_key_hex"]
.as_str()
.expect("seller pubkey")
@@ -393,11 +397,13 @@ fn listing_publish_and_update_use_durable_bridge_publish() {
match body["method"].as_str().unwrap_or_default() {
"nip46.session.list" => {
assert_eq!(auth_header, None);
- MockRpcResponse::success(json!([sample_session(
+ MockRpcResponse::success(json!([sample_session_with_authority(
"sess_publish_01",
seller_pubkey.as_str(),
&["sign_event"],
- true
+ true,
+ Some(account_id.as_str()),
+ Some("conn_listing_binding_01")
)]))
}
"bridge.listing.publish" => {
@@ -653,7 +659,10 @@ fn listing_publish_uses_myc_binding_before_resolving_daemon_signer_session() {
assert!(account_output.status.success());
let account_json: Value =
serde_json::from_slice(account_output.stdout.as_slice()).expect("account json");
- let account_id = account_json["account"]["id"].as_str().expect("account id");
+ let account_id = account_json["account"]["id"]
+ .as_str()
+ .expect("account id")
+ .to_owned();
let public_identity = account_json["public_identity"].clone();
let seller_pubkey = public_identity["public_key_hex"]
.as_str()
@@ -693,7 +702,11 @@ fn listing_publish_uses_myc_binding_before_resolving_daemon_signer_session() {
let myc = write_fake_myc(
dir.path(),
successful_status_script(
- sample_myc_status_payload(account_id, &public_identity, "conn_listing_binding_01")
+ sample_myc_status_payload(
+ account_id.as_str(),
+ &public_identity,
+ "conn_listing_binding_01",
+ )
.to_string(),
)
.as_str(),
@@ -701,16 +714,19 @@ fn listing_publish_uses_myc_binding_before_resolving_daemon_signer_session() {
let requests = Arc::new(Mutex::new(Vec::<Value>::new()));
let recorded = Arc::clone(&requests);
+ let session_account_id = account_id.clone();
let server = MockRpcServer::start(move |body, auth_header| {
recorded.lock().expect("recorded").push(body.clone());
match body["method"].as_str().unwrap_or_default() {
"nip46.session.list" => {
assert_eq!(auth_header, None);
- MockRpcResponse::success(json!([sample_session(
+ MockRpcResponse::success(json!([sample_session_with_authority(
"sess_publish_01",
seller_pubkey.as_str(),
&["sign_event"],
- true
+ true,
+ Some(session_account_id.as_str()),
+ Some("conn_listing_binding_01"),
)]))
}
"bridge.listing.publish" => {
@@ -763,7 +779,12 @@ managed_account_ref = "{account_id}"
.output()
.expect("run listing publish");
- assert!(output.status.success());
+ assert!(
+ output.status.success(),
+ "stdout:\n{}\n\nstderr:\n{}",
+ String::from_utf8_lossy(output.stdout.as_slice()),
+ String::from_utf8_lossy(output.stderr.as_slice())
+ );
let publish_json: Value = serde_json::from_slice(output.stdout.as_slice()).expect("json");
assert_eq!(publish_json["state"], "published");
assert_eq!(publish_json["signer_mode"], "nip46_session");
@@ -778,6 +799,18 @@ managed_account_ref = "{account_id}"
recorded[1]["params"]["signer_session_id"],
"sess_publish_01"
);
+ assert_eq!(
+ recorded[1]["params"]["signer_authority"]["provider_runtime_id"],
+ "myc"
+ );
+ assert_eq!(
+ recorded[1]["params"]["signer_authority"]["account_identity_id"],
+ account_id
+ );
+ assert_eq!(
+ recorded[1]["params"]["signer_authority"]["provider_signer_session_id"],
+ "conn_listing_binding_01"
+ );
}
#[test]
@@ -908,6 +941,136 @@ managed_account_ref = "{mismatch_account_id}"
}
#[test]
+fn listing_publish_rejects_daemon_session_with_mismatched_myc_authority() {
+ let _guard = listing_test_guard();
+ let dir = tempdir().expect("tempdir");
+ let init = cli_command_in(dir.path())
+ .args(["local", "init"])
+ .output()
+ .expect("run local init");
+ assert!(init.status.success());
+
+ let account_output = cli_command_in(dir.path())
+ .args(["--json", "account", "new"])
+ .output()
+ .expect("run account new");
+ assert!(account_output.status.success());
+ let account_json: Value =
+ serde_json::from_slice(account_output.stdout.as_slice()).expect("account json");
+ let account_id = account_json["account"]["id"]
+ .as_str()
+ .expect("account id")
+ .to_owned();
+ let public_identity = account_json["public_identity"].clone();
+ let seller_pubkey = public_identity["public_key_hex"]
+ .as_str()
+ .expect("seller pubkey")
+ .to_owned();
+ seed_farm(
+ dir.path(),
+ seller_pubkey.as_str(),
+ "AAAAAAAAAAAAAAAAAAAAAw",
+ "La Huerta",
+ );
+
+ let draft_path = dir.path().join("mismatched-authority.toml");
+ fs::write(
+ &draft_path,
+ valid_listing_draft(
+ "AAAAAAAAAAAAAAAAAAAAAg",
+ "",
+ "",
+ "eggs",
+ "Pasture eggs",
+ "Protein",
+ "Fresh pasture-raised eggs collected daily.",
+ "12",
+ "each",
+ "4.50",
+ "USD",
+ "1",
+ "each",
+ "18",
+ "pickup",
+ "La Huerta del Sur",
+ ),
+ )
+ .expect("write listing draft");
+
+ let myc = write_fake_myc(
+ dir.path(),
+ successful_status_script(
+ sample_myc_status_payload(
+ account_id.as_str(),
+ &public_identity,
+ "conn_listing_binding_03",
+ )
+ .to_string(),
+ )
+ .as_str(),
+ );
+
+ let requests = Arc::new(Mutex::new(Vec::<Value>::new()));
+ let recorded = Arc::clone(&requests);
+ let server = MockRpcServer::start(move |body, _auth_header| {
+ recorded.lock().expect("recorded").push(body.clone());
+ match body["method"].as_str().unwrap_or_default() {
+ "nip46.session.list" => MockRpcResponse::success(json!([sample_session_with_authority(
+ "sess_mismatch_01",
+ seller_pubkey.as_str(),
+ &["sign_event:30402"],
+ true,
+ Some("acct_wrong"),
+ Some("conn_listing_binding_03"),
+ )])),
+ _ => MockRpcResponse::rpc_error(-32601, "unexpected rpc method"),
+ }
+ });
+ write_workspace_config(
+ dir.path(),
+ workspace_config_with_write_plane(
+ format!(
+ r#"
+[[capability_binding]]
+capability = "signer.remote_nip46"
+provider = "myc"
+target_kind = "managed_instance"
+target = "default"
+managed_account_ref = "{account_id}"
+"#
+ )
+ .as_str(),
+ server.url().as_str(),
+ )
+ .as_str(),
+ );
+
+ let output = cli_command_in(dir.path())
+ .env("RADROOTS_RPC_BEARER_TOKEN", "bridge-secret")
+ .args([
+ "--json",
+ "--signer",
+ "myc",
+ "--myc-executable",
+ myc.to_str().expect("myc path"),
+ "listing",
+ "publish",
+ draft_path.to_str().expect("draft path"),
+ ])
+ .output()
+ .expect("run listing publish");
+
+ assert_eq!(output.status.code(), Some(3));
+ let publish_json: Value = serde_json::from_slice(output.stdout.as_slice()).expect("json");
+ assert_eq!(publish_json["state"], "unconfigured");
+ assert!(publish_json["reason"].as_str().is_some());
+
+ let recorded = requests.lock().expect("requests");
+ assert_eq!(recorded.len(), 1);
+ assert_eq!(recorded[0]["method"], "nip46.session.list");
+}
+
+#[test]
fn listing_publish_without_matching_signer_session_exits_unconfigured() {
let _guard = listing_test_guard();
let dir = tempdir().expect("tempdir");
@@ -1442,6 +1605,17 @@ fn sample_session(
permissions: &[&str],
authorized: bool,
) -> Value {
+ sample_session_with_authority(session_id, signer_pubkey, permissions, authorized, None, None)
+}
+
+fn sample_session_with_authority(
+ session_id: &str,
+ signer_pubkey: &str,
+ permissions: &[&str],
+ authorized: bool,
+ account_identity_id: Option<&str>,
+ provider_signer_session_id: Option<&str>,
+) -> Value {
json!({
"session_id": session_id,
"role": "remote_signer",
@@ -1452,7 +1626,12 @@ fn sample_session(
"permissions": permissions,
"auth_required": false,
"authorized": authorized,
- "expires_in_secs": Value::Null
+ "expires_in_secs": Value::Null,
+ "signer_authority": account_identity_id.map(|account_identity_id| json!({
+ "provider_runtime_id": "myc",
+ "account_identity_id": account_identity_id,
+ "provider_signer_session_id": provider_signer_session_id
+ }))
})
}
diff --git a/tests/order.rs b/tests/order.rs
@@ -361,7 +361,10 @@ fn order_new_creates_a_local_draft_with_selected_account_defaults() {
assert!(account_output.status.success());
let account_json: Value =
serde_json::from_slice(account_output.stdout.as_slice()).expect("account json");
- let account_id = account_json["account"]["id"].as_str().expect("account id");
+ let account_id = account_json["account"]["id"]
+ .as_str()
+ .expect("account id")
+ .to_owned();
let buyer_pubkey = account_json["public_identity"]["public_key_hex"]
.as_str()
.expect("buyer pubkey");
@@ -744,7 +747,10 @@ fn order_submit_uses_myc_binding_before_resolving_daemon_signer_session() {
assert!(account_output.status.success());
let account_json: Value =
serde_json::from_slice(account_output.stdout.as_slice()).expect("account json");
- let account_id = account_json["account"]["id"].as_str().expect("account id");
+ let account_id = account_json["account"]["id"]
+ .as_str()
+ .expect("account id")
+ .to_owned();
let public_identity = account_json["public_identity"].clone();
let buyer_pubkey = public_identity["public_key_hex"]
.as_str()
@@ -754,8 +760,12 @@ fn order_submit_uses_myc_binding_before_resolving_daemon_signer_session() {
let myc = write_fake_myc(
dir.path(),
successful_status_script(
- sample_myc_status_payload(account_id, &public_identity, "conn_order_binding_01")
- .to_string(),
+ sample_myc_status_payload(
+ account_id.as_str(),
+ &public_identity,
+ "conn_order_binding_01",
+ )
+ .to_string(),
)
.as_str(),
);
@@ -782,6 +792,7 @@ fn order_submit_uses_myc_binding_before_resolving_daemon_signer_session() {
let requests = Arc::new(Mutex::new(Vec::<MockRpcRequest>::new()));
let recorded = Arc::clone(&requests);
+ let session_account_id = account_id.clone();
let server = MockRpcServer::start(move |body, auth_header| {
recorded
.lock()
@@ -792,11 +803,13 @@ fn order_submit_uses_myc_binding_before_resolving_daemon_signer_session() {
auth_header,
});
match body["method"].as_str().unwrap_or_default() {
- "nip46.session.list" => MockRpcResponse::success(json!([sample_session(
+ "nip46.session.list" => MockRpcResponse::success(json!([sample_session_with_authority(
"sess_order_02",
buyer_pubkey.as_str(),
&["sign_event"],
- true
+ true,
+ Some(session_account_id.as_str()),
+ Some("conn_order_binding_01")
)])),
"bridge.order.request" => MockRpcResponse::success(serde_json::json!({
"deduplicated": false,
@@ -858,6 +871,18 @@ managed_account_ref = "{account_id}"
.find(|request| request.method == "bridge.order.request")
.expect("bridge order request");
assert_eq!(request.body["params"]["signer_session_id"], "sess_order_02");
+ assert_eq!(
+ request.body["params"]["signer_authority"]["provider_runtime_id"],
+ "myc"
+ );
+ assert_eq!(
+ request.body["params"]["signer_authority"]["account_identity_id"],
+ account_id
+ );
+ assert_eq!(
+ request.body["params"]["signer_authority"]["provider_signer_session_id"],
+ "conn_order_binding_01"
+ );
}
#[test]
@@ -1153,6 +1178,17 @@ fn sample_session(
permissions: &[&str],
authorized: bool,
) -> Value {
+ sample_session_with_authority(session_id, signer_pubkey, permissions, authorized, None, None)
+}
+
+fn sample_session_with_authority(
+ session_id: &str,
+ signer_pubkey: &str,
+ permissions: &[&str],
+ authorized: bool,
+ account_identity_id: Option<&str>,
+ provider_signer_session_id: Option<&str>,
+) -> Value {
json!({
"session_id": session_id,
"role": "remote_signer",
@@ -1163,7 +1199,12 @@ fn sample_session(
"permissions": permissions,
"auth_required": false,
"authorized": authorized,
- "expires_in_secs": Value::Null
+ "expires_in_secs": Value::Null,
+ "signer_authority": account_identity_id.map(|account_identity_id| json!({
+ "provider_runtime_id": "myc",
+ "account_identity_id": account_identity_id,
+ "provider_signer_session_id": provider_signer_session_id
+ }))
})
}