gui_server_api/navigation/
securitykeys.rs

1// SPDX-FileCopyrightText: 2024-2026 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! Security Keys navigation request and response formats.
5//!
6//! Currently the only navigation request is the user-presence prompt. Operation outcome
7//! notifications used to be a navigation request too, but were moved to a fido subscription
8//! event (`SubscribeOperationOutcomes`) since the Security Keys app is guaranteed to be
9//! running by the time an outcome fires.
10
11/// Unified navigation request enum for the Security Keys app.
12///
13/// Kept as an enum (rather than a bare struct) so future nav variants can be added
14/// without breaking the wire format.
15#[derive(Debug, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
16pub enum SecurityKeysNavRequest {
17    UserPresence(UserPresenceOptions),
18}
19
20impl SecurityKeysNavRequest {
21    pub fn from_slice(data: &[u8]) -> Option<Self> {
22        let Ok(archived) = rkyv::access::<ArchivedSecurityKeysNavRequest, rkyv::rancor::Error>(data) else {
23            return None;
24        };
25        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
26    }
27
28    pub fn serialize(&self) -> Vec<u8> {
29        rkyv::to_bytes::<rkyv::rancor::Error>(self).map(|b| b.to_vec()).unwrap_or_default()
30    }
31}
32
33/// Options for the User Presence navigation request.
34///
35/// ```rust,ignore
36/// # use gui_server_api::navigation::securitykeys::{UserPresenceOptions};
37/// let options = UserPresenceOptions::authentication(Some(0)).with_rp_id("foundation.xyz".to_string());
38/// ```
39#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
40pub struct UserPresenceOptions {
41    /// The security key index to use, or None to allow the user to select one.
42    pub security_key_index: Option<usize>,
43    pub authentication: bool,
44    pub rp_id: Option<String>,
45    pub rp_name: Option<String>,
46    pub user_name: Option<String>,
47    pub user_display_name: Option<String>,
48}
49
50impl UserPresenceOptions {
51    pub fn registration(security_key_index: Option<usize>) -> Self {
52        Self {
53            security_key_index,
54            authentication: false,
55            rp_id: None,
56            rp_name: None,
57            user_name: None,
58            user_display_name: None,
59        }
60    }
61
62    pub fn authentication(security_key_index: Option<usize>) -> Self {
63        Self {
64            security_key_index,
65            authentication: true,
66            rp_id: None,
67            rp_name: None,
68            user_name: None,
69            user_display_name: None,
70        }
71    }
72
73    pub fn with_rp_id(self, rp_id: String) -> Self { Self { rp_id: Some(rp_id), ..self } }
74
75    pub fn with_rp_name(self, rp_name: String) -> Self { Self { rp_name: Some(rp_name), ..self } }
76
77    pub fn with_user_name(self, user_name: String) -> Self { Self { user_name: Some(user_name), ..self } }
78
79    pub fn with_user_display_name(self, user_display_name: String) -> Self {
80        Self { user_display_name: Some(user_display_name), ..self }
81    }
82
83    pub fn from_slice(data: &[u8]) -> Option<Self> {
84        let Ok(archived) = rkyv::access::<ArchivedUserPresenceOptions, rkyv::rancor::Error>(data) else {
85            return None;
86        };
87        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
88    }
89
90    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
91}
92
93#[derive(Debug, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
94pub struct UserPresenceResult {
95    present: bool,
96    /// The selected key index when user confirms presence.
97    /// This is used when the initial security_key_index was None, allowing
98    /// the user to select a key during the user presence check.
99    selected_key_index: Option<usize>,
100}
101
102impl UserPresenceResult {
103    pub fn new_checked(selected_key_index: Option<usize>) -> Self {
104        UserPresenceResult { present: true, selected_key_index }
105    }
106
107    pub fn new_cancelled() -> Self { UserPresenceResult { present: false, selected_key_index: None } }
108
109    pub fn present(&self) -> bool { self.present }
110
111    pub fn selected_key_index(&self) -> Option<usize> { self.selected_key_index }
112
113    pub fn from_slice(data: &[u8]) -> Option<Self> {
114        let Ok(archived) = rkyv::access::<ArchivedUserPresenceResult, rkyv::rancor::Error>(data) else {
115            return None;
116        };
117        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
118    }
119
120    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
121}