Skip to main content

host_sso/
traits.rs

1use crate::product_key_cache::ProductKeyCacheEntry;
2use crate::state::SsoState;
3
4/// Transport adapter: topic-keyed publish/subscribe.
5///
6/// Hosts implement this to connect the SSO manager to a Statement Store
7/// or equivalent message bus (e.g. test stub, WebSocket relay).
8pub trait SsoTransport: Send + Sync {
9    /// Subscribe to messages on `topic_hex`.
10    ///
11    /// Returns `(subscription_id, receiver)` where each message is `(key, value)`.
12    /// The subscription lifetime is tied to the returned ID; call `unsubscribe` to cancel.
13    fn subscribe(
14        &self,
15        topic_hex: &str,
16    ) -> Result<(u64, std::sync::mpsc::Receiver<(String, String)>), String>;
17
18    /// Cancel a subscription by its ID.
19    fn unsubscribe(&self, id: u64);
20
21    /// Publish `value` to `topic_hex`.
22    fn write(&self, topic_hex: &str, value: &str) -> Result<(), String>;
23}
24
25/// Re-export of [`host_wallet::HostSigner`] — the unified signing trait.
26///
27/// SSO callers should provide an `SsoPathSigner` (from `host-wallet`) or
28/// any other `HostSigner` implementation that derives at `//wallet//sso`.
29pub use host_wallet::HostSigner as SsoSigner;
30
31/// Persistence adapter: session metadata storage.
32///
33/// Hosts implement this to persist a paired session across restarts (e.g.
34/// platform keychain, encrypted local file, in-memory for tests).
35pub trait SsoSessionStore: Send + Sync {
36    /// Persist `session` metadata, overwriting any existing entry.
37    fn save(&self, session: &PersistedSessionMeta) -> Result<(), String>;
38
39    /// Load the previously persisted session, or `None` if absent.
40    fn load(&self) -> Result<Option<PersistedSessionMeta>, String>;
41
42    /// Remove any persisted session.
43    fn clear(&self) -> Result<(), String>;
44}
45
46/// Event-sink adapter: push state changes to the UI layer.
47///
48/// Hosts implement this to forward `SsoState` updates to the UI (e.g. via a
49/// channel, UniFFI callback, or test recorder).
50pub trait SsoEventSink: Send + Sync {
51    /// Called whenever the manager transitions to a new `SsoState`.
52    fn on_state_changed(&self, state: &SsoState);
53
54    /// Called when the phone revokes a product key authorization.
55    ///
56    /// The default implementation is a no-op so existing `SsoEventSink`
57    /// implementors do not need to be updated.
58    fn on_product_key_revoked(&self, _product_id: &str, _index: u32) {}
59}
60
61/// The return type of [`SsoProductKeyStore::load`].
62///
63/// Contains the bound identity and its cached key entries, or `None` if the
64/// store is empty.
65pub type ProductKeyStoreLoadResult = Result<Option<([u8; 32], Vec<ProductKeyCacheEntry>)>, String>;
66
67/// Persistence adapter for the product key cache.
68///
69/// Separate from [`SsoSessionStore`] — the cache has its own keychain entry.
70/// Hosts implement this to persist the product key cache across restarts
71/// (e.g. platform keychain, encrypted local file, in-memory for tests).
72pub trait SsoProductKeyStore: Send + Sync {
73    /// Persist `entries` bound to `identity`. Overwrites any existing entry.
74    fn save(&self, identity: &[u8; 32], entries: &[ProductKeyCacheEntry]) -> Result<(), String>;
75
76    /// Load the previously persisted cache, or `None` if absent.
77    ///
78    /// Returns `(identity, entries)` so callers can detect staleness.
79    fn load(&self) -> ProductKeyStoreLoadResult;
80
81    /// Remove any persisted cache entry.
82    fn delete(&self) -> Result<(), String>;
83}
84
85/// No-op product key store for tests and WASM targets.
86#[derive(Debug, Default)]
87pub struct NoopProductKeyStore;
88
89impl SsoProductKeyStore for NoopProductKeyStore {
90    fn save(&self, _: &[u8; 32], _: &[ProductKeyCacheEntry]) -> Result<(), String> {
91        Ok(())
92    }
93
94    fn load(&self) -> ProductKeyStoreLoadResult {
95        Ok(None)
96    }
97
98    fn delete(&self) -> Result<(), String> {
99        Ok(())
100    }
101}
102
103/// Minimal session metadata that survives across process restarts.
104///
105/// The AES session key is NOT persisted here — it must be re-derived during
106/// the next pairing or kept in a platform-native secure enclave by the host.
107#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
108pub struct PersistedSessionMeta {
109    /// Opaque session identifier shared with the mobile wallet.
110    pub session_id: String,
111    /// SS58 address of the paired mobile account.
112    pub address: String,
113    /// Human-readable name shown during pairing (e.g. device name).
114    pub display_name: String,
115    /// Hex-encoded uncompressed P-256 public key (65 bytes, 0x04-prefixed).
116    pub p256_pubkey_hex: String,
117    /// Hex-encoded 32-byte sr25519 public key of the phone's wallet account.
118    ///
119    /// `None` for sessions established with older phones that do not send their
120    /// wallet pubkey during the pairing handshake.
121    #[serde(default)]
122    pub phone_wallet_pubkey_hex: Option<String>,
123    /// Capability tokens advertised by the phone during pairing.
124    ///
125    /// Currently defined token: `"product_key"` — phone supports
126    /// `ProductKeyRequest`/`ProductKeyResponse` over the encrypted channel.
127    #[serde(default)]
128    pub capabilities: Vec<String>,
129}