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