Skip to main content

breez_sdk_spark/session_store/
mod.rs

1//! User-facing [`SessionStore`] surface for the Breez SDK.
2//!
3//! UniFFI-generated bindings can only export traits defined inside the crate
4//! they're generated from, so we re-declare the trait + supporting types
5//! here. The DB-backed implementations (`PostgresSessionStore`,
6//! `MysqlSessionStore`) implement `spark_wallet::SessionStore` directly
7//! and are picked up by `SdkBuilder::build()` when a corresponding pool is
8//! configured on the `SdkContext`.
9//!
10//! Internal layering applied automatically by `SdkBuilder::build()`:
11//!
12//! ```text
13//! auth providers (SO / SSP)
14//!     │
15//!     ▼
16//! CachingSessionStore   ← in-memory hot path
17//!     │
18//!     ▼
19//! PostgresSessionStore | MysqlSessionStore | InMemorySessionStore
20//!     (or a SessionStore supplied via SdkBuilder::with_session_store)
21//! ```
22//!
23//! The SDK stores tokens as-is and applies no transformation. To change how
24//! tokens are persisted or to transform them (for example at-rest encryption,
25//! which the SDK does not apply itself), supply a [`SessionStore`] via
26//! `SdkBuilder::with_session_store`, wrapping the backend's own store from
27//! `default_session_store` to keep persistence.
28
29mod adapter;
30mod caching;
31
32use bitcoin::secp256k1::PublicKey;
33use thiserror::Error;
34
35pub use adapter::SessionStoreAdapter;
36pub(crate) use adapter::SparkSessionStoreAdapter;
37pub(crate) use caching::CachingSessionStore;
38
39#[cfg(feature = "uniffi")]
40uniffi::custom_type!(PublicKey, String, {
41    remote,
42    try_lift: |val| {
43        use std::str::FromStr;
44        PublicKey::from_str(&val).map_err(uniffi::deps::anyhow::Error::msg)
45    },
46    lower: |obj| obj.to_string(),
47});
48
49#[derive(Debug, Error, Clone)]
50#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
51pub enum SessionStoreError {
52    #[error("Session not found")]
53    NotFound,
54    #[error("Generic error: {0}")]
55    Generic(String),
56}
57
58impl From<spark_wallet::SessionStoreError> for SessionStoreError {
59    fn from(e: spark_wallet::SessionStoreError) -> Self {
60        match e {
61            spark_wallet::SessionStoreError::NotFound => SessionStoreError::NotFound,
62            spark_wallet::SessionStoreError::Generic(msg) => SessionStoreError::Generic(msg),
63        }
64    }
65}
66
67impl From<SessionStoreError> for spark_wallet::SessionStoreError {
68    fn from(e: SessionStoreError) -> Self {
69        match e {
70            SessionStoreError::NotFound => spark_wallet::SessionStoreError::NotFound,
71            SessionStoreError::Generic(msg) => spark_wallet::SessionStoreError::Generic(msg),
72        }
73    }
74}
75
76/// Cached authentication session for a single backend service identity.
77#[derive(Clone)]
78#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
79pub struct Session {
80    pub token: String,
81    pub expiration: u64,
82    pub refresh_token: Option<String>,
83    pub refresh_token_expiration: Option<u64>,
84}
85
86impl From<spark_wallet::Session> for Session {
87    fn from(s: spark_wallet::Session) -> Self {
88        Self {
89            token: s.token,
90            expiration: s.expiration,
91            refresh_token: s.refresh_token,
92            refresh_token_expiration: s.refresh_token_expiration,
93        }
94    }
95}
96
97impl From<Session> for spark_wallet::Session {
98    fn from(s: Session) -> Self {
99        Self {
100            token: s.token,
101            expiration: s.expiration,
102            refresh_token: s.refresh_token,
103            refresh_token_expiration: s.refresh_token_expiration,
104        }
105    }
106}
107
108/// Persistent storage for authentication sessions, keyed by the service's
109/// identity public key. Implementations should be thread-safe and may be
110/// backed by an in-memory map (default) or a shared database for cross-pod
111/// auth sharing.
112#[cfg_attr(feature = "uniffi", uniffi::export(with_foreign))]
113#[macros::async_trait]
114pub trait SessionStore: Send + Sync {
115    async fn get_session(
116        &self,
117        service_identity_key: PublicKey,
118    ) -> Result<Session, SessionStoreError>;
119
120    async fn set_session(
121        &self,
122        service_identity_key: PublicKey,
123        session: Session,
124    ) -> Result<(), SessionStoreError>;
125}