commit e0207936c5894343236f10a29640bdcf82352d7e
parent ce88bb96fd54ce45893521de1ee5defaba2a33e5
Author: triesap <tyson@radroots.org>
Date: Sat, 28 Mar 2026 18:14:43 +0000
app: purge remote signer state during reset
Diffstat:
8 files changed, 83 insertions(+), 1 deletion(-)
diff --git a/crates/android/src/lib.rs b/crates/android/src/lib.rs
@@ -875,6 +875,7 @@ impl AndroidBackend {
manager: &RadrootsNostrAccountsManager,
accounts_path: &Path,
) -> Result<IdentityGateState, String> {
+ remote_signer::purge_all_custody_state()?;
let state = Self::remove_all_local_identities(manager)?;
Self::remove_accounts_file_if_present(accounts_path)?;
Ok(state)
diff --git a/crates/android/src/remote_signer.rs b/crates/android/src/remote_signer.rs
@@ -9,6 +9,7 @@ use radroots_app_remote_signer::{
RadrootsAppRemoteSignerPendingSession, RadrootsAppRemoteSignerSessionRecord,
RadrootsAppRemoteSignerSessionStoreState, radroots_app_remote_signer_clear_pending_session,
radroots_app_remote_signer_disconnect_selected, radroots_app_remote_signer_preview,
+ radroots_app_remote_signer_purge_all_custody_state,
radroots_app_remote_signer_reconcile_startup,
};
use radroots_identity::RadrootsIdentityId;
@@ -203,6 +204,11 @@ pub(crate) fn cancel_pending_connection() -> Result<(), String> {
Ok(())
}
+pub(crate) fn purge_all_custody_state() -> Result<(), String> {
+ let store_path = sessions_path()?;
+ radroots_app_remote_signer_purge_all_custody_state(store_path.as_path(), remove_client_secret)
+}
+
fn activate_remote_session(
client_account_id: &str,
user_identity: radroots_identity::RadrootsIdentityPublic,
diff --git a/crates/desktop/src/main.rs b/crates/desktop/src/main.rs
@@ -361,6 +361,7 @@ impl DesktopBackend {
manager: &RadrootsNostrAccountsManager,
accounts_path: &Path,
) -> Result<IdentityGateState, String> {
+ remote_signer::purge_all_custody_state()?;
let state = Self::remove_all_local_identities(manager)?;
Self::remove_accounts_file_if_present(accounts_path)?;
Ok(state)
diff --git a/crates/desktop/src/remote_signer.rs b/crates/desktop/src/remote_signer.rs
@@ -9,6 +9,7 @@ use radroots_app_remote_signer::{
RadrootsAppRemoteSignerPendingSession, RadrootsAppRemoteSignerSessionRecord,
RadrootsAppRemoteSignerSessionStoreState, radroots_app_remote_signer_clear_pending_session,
radroots_app_remote_signer_disconnect_selected, radroots_app_remote_signer_preview,
+ radroots_app_remote_signer_purge_all_custody_state,
radroots_app_remote_signer_reconcile_startup,
};
use radroots_nostr_accounts::prelude::{
@@ -202,6 +203,11 @@ pub(crate) fn cancel_pending_connection() -> Result<(), String> {
Ok(())
}
+pub(crate) fn purge_all_custody_state() -> Result<(), String> {
+ let store_path = sessions_path()?;
+ radroots_app_remote_signer_purge_all_custody_state(store_path.as_path(), remove_client_secret)
+}
+
fn activate_remote_session(
client_account_id: &str,
user_identity: radroots_identity::RadrootsIdentityPublic,
diff --git a/crates/ios/src/lib.rs b/crates/ios/src/lib.rs
@@ -316,6 +316,7 @@ impl IosBackend {
manager: &RadrootsNostrAccountsManager,
accounts_path: &Path,
) -> Result<IdentityGateState, String> {
+ remote_signer::purge_all_custody_state()?;
let state = Self::remove_all_local_identities(manager)?;
Self::remove_accounts_file_if_present(accounts_path)?;
Ok(state)
diff --git a/crates/ios/src/remote_signer.rs b/crates/ios/src/remote_signer.rs
@@ -9,6 +9,7 @@ use radroots_app_remote_signer::{
RadrootsAppRemoteSignerPendingSession, RadrootsAppRemoteSignerSessionRecord,
RadrootsAppRemoteSignerSessionStoreState, radroots_app_remote_signer_clear_pending_session,
radroots_app_remote_signer_disconnect_selected, radroots_app_remote_signer_preview,
+ radroots_app_remote_signer_purge_all_custody_state,
radroots_app_remote_signer_reconcile_startup,
};
use radroots_identity::RadrootsIdentityId;
@@ -203,6 +204,11 @@ pub(crate) fn cancel_pending_connection() -> Result<(), String> {
Ok(())
}
+pub(crate) fn purge_all_custody_state() -> Result<(), String> {
+ let store_path = sessions_path()?;
+ radroots_app_remote_signer_purge_all_custody_state(store_path.as_path(), remove_client_secret)
+}
+
fn activate_remote_session(
client_account_id: &str,
user_identity: radroots_identity::RadrootsIdentityPublic,
diff --git a/crates/remote-signer/src/custody.rs b/crates/remote-signer/src/custody.rs
@@ -126,6 +126,18 @@ pub fn radroots_app_remote_signer_reconcile_startup(
Ok(())
}
+pub fn radroots_app_remote_signer_purge_all_custody_state(
+ path: &Path,
+ remove_client_secret: impl Fn(&str) -> Result<(), String>,
+) -> Result<(), String> {
+ let load = load_sessions_with_recovery(path)?;
+ for record in &load.state.sessions {
+ remove_client_secret(record.client_account_id())?;
+ }
+ remove_sessions_file_if_present(path)?;
+ Ok(())
+}
+
fn remote_signer_public_only_accounts(
manager: &RadrootsNostrAccountsManager,
accounts: &[RadrootsNostrAccountRecord],
@@ -165,6 +177,16 @@ fn save_sessions(
state.save(path).map_err(|error| error.to_string())
}
+fn remove_sessions_file_if_present(path: &Path) -> Result<(), String> {
+ match std::fs::remove_file(path) {
+ Ok(()) => Ok(()),
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
+ Err(error) => Err(format!(
+ "failed to remove remote signer session store: {error}"
+ )),
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -350,4 +372,41 @@ mod tests {
.is_empty()
);
}
+
+ #[test]
+ fn purge_all_custody_state_removes_all_tracked_client_secrets_and_session_file() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let path = temp.path().join("sessions.json");
+ let pending = write_pending_state(path.as_path());
+ let mut active = write_active_state(path.as_path());
+ active.client_identity = fixture_public(&FIXTURE_BOB);
+ let mut state =
+ RadrootsAppRemoteSignerSessionStoreState::load(path.as_path()).expect("load");
+ state.sessions.push(active.clone());
+ state.save(path.as_path()).expect("save");
+
+ let vault = RadrootsNostrSecretVaultMemory::new();
+ secret_store_secret(&vault, pending.client_account_id(), "pending");
+ secret_store_secret(&vault, active.client_account_id(), "active");
+
+ radroots_app_remote_signer_purge_all_custody_state(
+ path.as_path(),
+ secret_remover(vault.clone()),
+ )
+ .expect("purge");
+
+ assert!(!path.exists());
+ assert!(
+ vault
+ .load_secret_hex(&fixture_account_id(pending.client_account_id()))
+ .expect("pending removed")
+ .is_none()
+ );
+ assert!(
+ vault
+ .load_secret_hex(&fixture_account_id(active.client_account_id()))
+ .expect("active removed")
+ .is_none()
+ );
+ }
}
diff --git a/crates/remote-signer/src/lib.rs b/crates/remote-signer/src/lib.rs
@@ -10,7 +10,9 @@ mod session;
pub use controller::{RadrootsAppRemoteSignerController, RadrootsAppRemoteSignerControllerHooks};
pub use custody::{
radroots_app_remote_signer_clear_pending_session,
- radroots_app_remote_signer_disconnect_selected, radroots_app_remote_signer_reconcile_startup,
+ radroots_app_remote_signer_disconnect_selected,
+ radroots_app_remote_signer_purge_all_custody_state,
+ radroots_app_remote_signer_reconcile_startup,
};
pub use error::RadrootsAppRemoteSignerError;
pub use input::{