Skip to main content

ap_client/
traits.rs

1use ap_noise::MultiDeviceTransport;
2use ap_proxy_protocol::{IdentityFingerprint, IdentityKeyPair};
3use async_trait::async_trait;
4
5use crate::error::RemoteClientError;
6
7/// Trait for session cache storage implementations
8///
9/// Provides an abstraction for storing and retrieving approved remote fingerprints.
10/// Implementations must be thread-safe for use in async contexts.
11pub trait SessionStore: Send + Sync {
12    /// Check if a fingerprint exists in the cache
13    fn has_session(&self, fingerprint: &IdentityFingerprint) -> bool;
14
15    /// Cache a new session fingerprint
16    ///
17    /// If the fingerprint already exists, updates the cached_at timestamp.
18    fn cache_session(&mut self, fingerprint: IdentityFingerprint) -> Result<(), RemoteClientError>;
19
20    /// Remove a fingerprint from the cache
21    fn remove_session(
22        &mut self,
23        fingerprint: &IdentityFingerprint,
24    ) -> Result<(), RemoteClientError>;
25
26    /// Clear all cached sessions
27    fn clear(&mut self) -> Result<(), RemoteClientError>;
28
29    /// List all cached sessions
30    ///
31    /// Returns tuples of (fingerprint, optional_name, created_timestamp, last_connected_timestamp)
32    fn list_sessions(&self) -> Vec<(IdentityFingerprint, Option<String>, u64, u64)>;
33
34    /// Set a friendly name for a cached session
35    fn set_session_name(
36        &mut self,
37        fingerprint: &IdentityFingerprint,
38        name: String,
39    ) -> Result<(), RemoteClientError>;
40
41    /// Update the last_connected_at timestamp for a session
42    fn update_last_connected(
43        &mut self,
44        fingerprint: &IdentityFingerprint,
45    ) -> Result<(), RemoteClientError>;
46
47    /// Save transport state for a session
48    ///
49    /// This allows session resumption without requiring a new Noise handshake.
50    fn save_transport_state(
51        &mut self,
52        fingerprint: &IdentityFingerprint,
53        transport_state: MultiDeviceTransport,
54    ) -> Result<(), RemoteClientError>;
55
56    /// Load transport state for a session
57    ///
58    /// Returns None if no transport state is stored for this session.
59    fn load_transport_state(
60        &self,
61        fingerprint: &IdentityFingerprint,
62    ) -> Result<Option<MultiDeviceTransport>, RemoteClientError>;
63}
64
65/// Provides a cryptographic identity for the current client.
66///
67/// For the device group, this should be one shared identity, for the single-device, a unique identity.
68/// This should be generated on first run and stored persistently, in secure storage where possible.
69pub trait IdentityProvider: Send + Sync {
70    /// Get reference to the identity keypair
71    fn identity(&self) -> &IdentityKeyPair;
72
73    /// Get the fingerprint of this identity
74    fn fingerprint(&self) -> IdentityFingerprint {
75        self.identity().identity().fingerprint()
76    }
77}
78
79/// How a new connection was established between devices.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum AuditConnectionType {
82    /// Paired using a 9-character rendezvous code exchanged out-of-band.
83    /// Requires explicit fingerprint verification before the connection is trusted.
84    Rendezvous,
85    /// Paired using a pre-shared key (PSK) token.
86    /// Trust is established through the shared secret — no fingerprint verification needed.
87    Psk,
88}
89
90/// Describes which credential fields were included in an approved response.
91///
92/// Contains only presence flags, never actual credential values.
93/// Useful for audit trails that need to record *what kind* of data was shared
94/// without logging sensitive material.
95#[derive(Debug, Clone, Default)]
96pub struct CredentialFieldSet {
97    pub has_username: bool,
98    pub has_password: bool,
99    pub has_totp: bool,
100    pub has_uri: bool,
101    pub has_notes: bool,
102}
103
104/// Audit events emitted by the [`UserClient`] (trusted device) for security-relevant actions.
105///
106/// Each variant represents a discrete, auditable action in the access protocol.
107/// Implementations of [`AuditLog`] receive these events and can persist them to files,
108/// databases, or external services.
109///
110/// ## Field conventions
111///
112/// - `remote_identity` — the [`IdentityFingerprint`] of the remote (untrusted) device.
113///   This is a stable 32-byte identifier derived from the device's persistent public key.
114/// - `remote_name` — optional human-friendly label assigned by the user when pairing
115///   (e.g., "Work Laptop"). Only available on connection events.
116/// - `request_id` — unique per-request correlation token generated by the remote client.
117///   Use this to correlate `CredentialRequested` → `CredentialApproved`/`CredentialDenied`.
118/// - `query` — the [`CredentialQuery`](crate::CredentialQuery) that triggered the lookup.
119/// - `domain` — the credential's domain (from the matched vault item), if available.
120///
121/// This enum is `#[non_exhaustive]` — new variants may be added in future versions.
122/// Implementations should include a `_ => {}` catch-all arm when matching.
123#[derive(Debug, Clone)]
124#[non_exhaustive]
125pub enum AuditEvent<'a> {
126    /// A new remote device completed the Noise handshake and was accepted as trusted.
127    ///
128    /// Emitted once per new pairing, after the session is cached. For rendezvous connections,
129    /// this fires only after the user has explicitly approved the fingerprint verification.
130    /// For PSK connections, trust is implicit via the shared secret.
131    ConnectionEstablished {
132        remote_identity: &'a IdentityFingerprint,
133        remote_name: Option<&'a str>,
134        connection_type: AuditConnectionType,
135    },
136
137    /// A previously-paired device reconnected and refreshed its transport keys.
138    ///
139    /// This is a routine reconnection of an already-trusted device — no user approval
140    /// is needed. The Noise handshake runs again to derive fresh encryption keys,
141    /// but the device was already verified during the original pairing.
142    SessionRefreshed {
143        remote_identity: &'a IdentityFingerprint,
144    },
145
146    /// A new connection attempt was rejected during fingerprint verification.
147    ///
148    /// The user was shown the handshake fingerprint and chose to reject it,
149    /// meaning the remote device was not added to the trusted session cache.
150    /// Only applies to rendezvous connections (PSK connections skip verification).
151    ConnectionRejected {
152        remote_identity: &'a IdentityFingerprint,
153    },
154
155    /// A remote device sent a request for credentials.
156    ///
157    /// Emitted when the encrypted request is received and decrypted. At this point
158    /// the request is pending user approval — no credential data has been shared yet.
159    CredentialRequested {
160        query: &'a crate::CredentialQuery,
161        remote_identity: &'a IdentityFingerprint,
162        request_id: &'a str,
163    },
164
165    /// A credential request was approved and the credential was sent to the remote device.
166    ///
167    /// The `fields` indicate which credential fields were included (e.g., username,
168    /// password, TOTP) without revealing the actual values.
169    CredentialApproved {
170        query: &'a crate::CredentialQuery,
171        domain: Option<&'a str>,
172        remote_identity: &'a IdentityFingerprint,
173        request_id: &'a str,
174        credential_id: Option<&'a str>,
175        fields: CredentialFieldSet,
176    },
177
178    /// A credential request was denied by the user.
179    ///
180    /// No credential data was sent to the remote device.
181    CredentialDenied {
182        query: &'a crate::CredentialQuery,
183        domain: Option<&'a str>,
184        remote_identity: &'a IdentityFingerprint,
185        request_id: &'a str,
186        credential_id: Option<&'a str>,
187    },
188}
189
190/// Persistent audit logging for security-relevant events on the UserClient.
191///
192/// Implementations may write to files, databases, or external services.
193/// All methods receive `&self` (interior mutability is the implementor's
194/// responsibility). Implementations should handle errors internally
195/// (e.g., log a warning via `tracing`). Timestamps are the implementor's
196/// responsibility.
197#[async_trait]
198pub trait AuditLog: Send + Sync {
199    /// Write an audit event
200    async fn write(&self, event: AuditEvent<'_>);
201}
202
203/// No-op audit logger that discards all events.
204/// Used as the default when no audit logging is configured.
205pub struct NoOpAuditLog;
206
207#[async_trait]
208impl AuditLog for NoOpAuditLog {
209    async fn write(&self, _event: AuditEvent<'_>) {}
210}