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;