lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 91b866b244354e142753db8c95da09ed8716f72a
parent 21355190b58bfde5cf257376b43a24d9b3705238
Author: triesap <tyson@radroots.org>
Date:   Sat, 18 Jul 2026 02:27:43 +0000

nostr_signer: finalize replay audit decisions

- replace the challenged request audit during authorized replay
- preserve one durable audit row per NIP-46 request identifier
- reject replay audit replacement across connection or method boundaries
- cover challenged-to-allowed replay persistence in the manager tests

Diffstat:
Mcrates/nostr_signer/src/manager.rs | 46+++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 41 insertions(+), 5 deletions(-)

diff --git a/crates/nostr_signer/src/manager.rs b/crates/nostr_signer/src/manager.rs @@ -870,7 +870,7 @@ impl RadrootsNostrSignerManager { action.audit_message(), request_at_unix, ); - state.audit_records.push(audit.clone()); + replace_or_insert_auth_replay_audit(state, audit.clone())?; Ok(RadrootsNostrSignerRequestEvaluation { request_id, @@ -1190,6 +1190,28 @@ fn now_unix_secs() -> u64 { .unwrap_or(0) } +fn replace_or_insert_auth_replay_audit( + state: &mut RadrootsNostrSignerStoreState, + replacement: RadrootsNostrSignerRequestAuditRecord, +) -> Result<(), RadrootsNostrSignerError> { + let Some(existing) = state + .audit_records + .iter_mut() + .find(|record| record.request_id == replacement.request_id) + else { + state.audit_records.push(replacement); + return Ok(()); + }; + if existing.connection_id != replacement.connection_id || existing.method != replacement.method + { + return Err(RadrootsNostrSignerError::InvalidState( + "auth replay audit does not match the original request".into(), + )); + } + *existing = replacement; + Ok(()) +} + #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { @@ -2370,16 +2392,23 @@ mod tests { format!("{}/flow", api_primary_https()).as_str(), ) .expect("require auth"); - let pending = manager - .set_pending_request( + let challenged = manager + .evaluate_request( &record.connection_id, RadrootsNostrConnectRequestMessage::new( "req-auth-preview", RadrootsNostrConnectRequest::GetPublicKey, ), ) - .expect("set pending"); - let pending_request = pending.pending_request.expect("pending request"); + .expect("evaluate challenged request"); + assert_eq!( + challenged.audit.decision, + RadrootsNostrSignerRequestDecision::Challenged + ); + let pending_request = challenged + .connection + .pending_request + .expect("pending request"); let workflow = manager .begin_auth_replay_publish_finalization(&record.connection_id) @@ -2408,6 +2437,13 @@ mod tests { .expect("stored"); assert_eq!(stored.auth_state, RadrootsNostrSignerAuthState::Pending); assert_eq!(stored.pending_request.as_ref(), Some(&pending_request)); + let audits = manager.list_audit_records().expect("list audits"); + assert_eq!(audits.len(), 1); + assert_eq!(audits[0].request_id.as_str(), "req-auth-preview"); + assert_eq!( + audits[0].decision, + RadrootsNostrSignerRequestDecision::Allowed + ); } #[test]