Skip to main content

nmrs/agent/
mod.rs

1//! NetworkManager secret agent for credential prompting over D-Bus.
2//!
3//! When NetworkManager needs credentials it does not already have — a Wi-Fi
4//! password was forgotten, a VPN token expired, an 802.1X password is required
5//! — it calls every registered **secret agent** via D-Bus. This module lets
6//! `nmrs` consumers register such an agent and respond to those requests
7//! without touching raw D-Bus.
8//!
9//! # Three-stream model
10//!
11//! [`SecretAgentBuilder::register()`](crate::agent::SecretAgentBuilder::register)
12//! returns a handle and three logical streams:
13//!
14//! 1. **Request stream** — the primary
15//!    [`mpsc::Receiver<SecretRequest>`](futures::channel::mpsc::Receiver)
16//!    returned alongside the handle. Each item is a credential prompt from
17//!    NetworkManager. Respond through the attached
18//!    [`SecretResponder`](crate::agent::SecretResponder).
19//!
20//! 2. **Cancellation stream** — accessed via
21//!    [`SecretAgentHandle::cancellations()`](crate::agent::SecretAgentHandle::cancellations). Yields
22//!    [`CancelReason`](crate::agent::CancelReason) items when
23//!    NetworkManager aborts a pending request. The agent replies to
24//!    NetworkManager automatically; this stream exists so the consumer can
25//!    tear down any UI it may have shown.
26//!
27//! 3. **Store event stream** — accessed via
28//!    [`SecretAgentHandle::store_events()`](crate::agent::SecretAgentHandle::store_events). Yields
29//!    [`SecretStoreEvent`](crate::agent::SecretStoreEvent) items when
30//!    NetworkManager asks the agent to save or delete persisted secrets.
31//!    Since `nmrs` delegates persistence to the consumer, these events are
32//!    optional and the agent always acknowledges them.
33//!
34//! # Lifecycle
35//!
36//! ```text
37//! SecretAgent::builder()
38//!     .with_identifier("com.example.MyApp")
39//!     .register().await?
40//!         │
41//!         ├── (SecretAgentHandle, request stream)
42//!         │
43//!         │   ┌──────── consumer loop ────────┐
44//!         │   │ while let Some(req) = rx … {  │
45//!         │   │   req.responder.wifi_psk(…)   │
46//!         │   │ }                             │
47//!         │   └───────────────────────────────┘
48//!         │
49//!         └── handle.unregister().await?
50//! ```
51//!
52//! If NetworkManager restarts while the agent is running, call
53//! [`SecretAgentHandle::reregister()`](crate::agent::SecretAgentHandle::reregister)
54//! to re-register.
55//!
56//! # Example
57//!
58//! ```no_run
59//! use futures::StreamExt;
60//! use nmrs::agent::{SecretAgent, SecretAgentFlags, SecretSetting};
61//!
62//! # async fn run() -> nmrs::Result<()> {
63//! let (handle, mut requests) = SecretAgent::builder()
64//!     .with_identifier("com.example.demo")
65//!     .register()
66//!     .await?;
67//!
68//! while let Some(req) = requests.next().await {
69//!     if !req.flags.contains(SecretAgentFlags::ALLOW_INTERACTION) {
70//!         req.responder.no_secrets().await?;
71//!         continue;
72//!     }
73//!     match req.setting {
74//!         SecretSetting::WifiPsk { ref ssid } => {
75//!             println!("Password needed for {ssid}");
76//!             req.responder.wifi_psk("secret").await?;
77//!         }
78//!         _ => req.responder.cancel().await?,
79//!     }
80//! }
81//!
82//! handle.unregister().await?;
83//! # Ok(())
84//! # }
85//! ```
86
87mod builder;
88pub(crate) mod iface;
89mod request;
90
91pub use builder::{SecretAgent, SecretAgentBuilder, SecretAgentHandle};
92pub use request::{
93    CancelReason, SecretAgentCapabilities, SecretAgentFlags, SecretRequest, SecretResponder,
94    SecretSetting, SecretStoreEvent,
95};
96
97/// Type alias so agent consumers only need one error type.
98pub type AgentError = crate::ConnectionError;