Skip to main content

ap_client/
types.rs

1//! Types for the remote client protocol
2
3use std::fmt;
4
5use ap_noise::Psk;
6use ap_proxy_protocol::IdentityFingerprint;
7use serde::{Deserialize, Serialize};
8
9/// What kind of credential to look up.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub enum CredentialQuery {
13    /// Look up by domain / URL.
14    Domain(String),
15    /// Look up by vault item ID.
16    Id(String),
17    /// Free-text search.
18    Search(String),
19}
20
21impl CredentialQuery {
22    /// Extract the inner search string from any query variant.
23    pub fn search_string(&self) -> &str {
24        match self {
25            Self::Domain(d) => d.as_str(),
26            Self::Id(id) => id.as_str(),
27            Self::Search(s) => s.as_str(),
28        }
29    }
30}
31
32impl fmt::Display for CredentialQuery {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            CredentialQuery::Domain(d) => write!(f, "domain: {d}"),
36            CredentialQuery::Id(id) => write!(f, "id: {id}"),
37            CredentialQuery::Search(s) => write!(f, "search: {s}"),
38        }
39    }
40}
41
42/// Connection mode for establishing a connection
43#[derive(Debug, Clone)]
44pub enum ConnectionMode {
45    /// New connection requiring rendezvous code pairing
46    New { rendezvous_code: String },
47    /// New connection using PSK authentication
48    NewPsk {
49        psk: Psk,
50        remote_fingerprint: IdentityFingerprint,
51    },
52    /// Existing connection using cached remote fingerprint
53    Existing {
54        remote_fingerprint: IdentityFingerprint,
55    },
56}
57
58pub enum ClientAction {
59    Accept,
60}
61
62/// Responses from CLI to RemoteClient
63#[derive(Debug, Clone)]
64pub enum RemoteClientResponse {
65    /// Response to fingerprint verification prompt
66    VerifyFingerprint {
67        /// Whether user confirmed fingerprint matches
68        approved: bool,
69    },
70}
71
72/// Events emitted by the remote client during connection and operation
73#[derive(Debug, Clone)]
74pub enum RemoteClientEvent {
75    /// Connecting to the proxy server
76    Connecting {
77        /// The proxy URL being connected to
78        proxy_url: String,
79    },
80    /// Successfully connected to the proxy
81    Connected {
82        /// The device's identity fingerprint (hex-encoded)
83        fingerprint: IdentityFingerprint,
84    },
85    /// Reconnecting to an existing session
86    ReconnectingToSession {
87        /// The fingerprint being reconnected to
88        fingerprint: IdentityFingerprint,
89    },
90    /// Rendezvous code resolution starting
91    RendevouzResolving {
92        /// The rendezvous code being resolved
93        code: String,
94    },
95    /// Rendezvous code resolved to fingerprint
96    RendevouzResolved {
97        /// The resolved identity fingerprint
98        fingerprint: IdentityFingerprint,
99    },
100    /// Using PSK mode for connection
101    PskMode {
102        /// The fingerprint being connected to
103        fingerprint: IdentityFingerprint,
104    },
105    /// Noise handshake starting
106    HandshakeStart,
107    /// Noise handshake progress
108    HandshakeProgress {
109        /// Progress message
110        message: String,
111    },
112    /// Noise handshake complete
113    HandshakeComplete,
114    /// Handshake fingerprint ready for verification
115    HandshakeFingerprint {
116        /// The 6-character hex fingerprint
117        fingerprint: String,
118    },
119    /// User verified the fingerprint
120    FingerprintVerified,
121    /// User rejected the fingerprint
122    FingerprintRejected {
123        /// Reason for rejection
124        reason: String,
125    },
126    /// Client is ready for credential requests
127    Ready {
128        /// Whether credentials can be requested
129        can_request_credentials: bool,
130    },
131    /// Credential request was sent
132    CredentialRequestSent {
133        /// The query used for the request
134        query: CredentialQuery,
135    },
136    /// Credential was received
137    CredentialReceived {
138        /// The credential data
139        credential: CredentialData,
140    },
141    /// An error occurred
142    Error {
143        /// Error message
144        message: String,
145        /// Context where error occurred
146        context: Option<String>,
147    },
148    /// Client was disconnected
149    Disconnected {
150        /// Reason for disconnection
151        reason: Option<String>,
152    },
153}
154
155/// Credential data returned from a request
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct CredentialData {
159    /// Username for the credential
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub username: Option<String>,
162    /// Password for the credential
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub password: Option<String>,
165    /// TOTP code if available
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub totp: Option<String>,
168    /// URI associated with the credential
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub uri: Option<String>,
171    /// Additional notes
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub notes: Option<String>,
174    /// Vault item ID
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub credential_id: Option<String>,
177    /// Domain associated with this credential
178    #[serde(default)]
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub domain: Option<String>,
181}
182
183/// Internal protocol messages sent over WebSocket
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(tag = "type", rename_all = "kebab-case")]
186pub(crate) enum ProtocolMessage {
187    /// Noise handshake init (initiator -> responder)
188    #[serde(rename = "handshake-init")]
189    HandshakeInit { data: String, ciphersuite: String },
190    /// Noise handshake response (responder -> initiator)
191    #[serde(rename = "handshake-response")]
192    HandshakeResponse { data: String, ciphersuite: String },
193    /// Encrypted credential request
194    CredentialRequest { encrypted: String },
195    /// Encrypted credential response
196    CredentialResponse { encrypted: String },
197}
198
199/// Internal credential request structure (encrypted in transit)
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub(crate) struct CredentialRequestPayload {
202    #[serde(rename = "type")]
203    pub request_type: String,
204    pub query: CredentialQuery,
205    pub timestamp: u64,
206    #[serde(rename = "requestId")]
207    pub request_id: String,
208}
209
210/// Internal credential response structure (encrypted in transit)
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub(crate) struct CredentialResponsePayload {
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub credential: Option<CredentialData>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub error: Option<String>,
217    #[serde(rename = "requestId")]
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub request_id: Option<String>,
220}