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}
83
84impl From<spark_wallet::Session> for Session {
85    fn from(s: spark_wallet::Session) -> Self {
86        Self {
87            token: s.token,
88            expiration: s.expiration,
89        }
90    }
91}
92
93impl From<Session> for spark_wallet::Session {
94    fn from(s: Session) -> Self {
95        Self {
96            token: s.token,
97            expiration: s.expiration,
98        }
99    }
100}
101
102/// Persistent storage for authentication sessions, keyed by the service's
103/// identity public key. Implementations should be thread-safe and may be
104/// backed by an in-memory map (default) or a shared database for cross-pod
105/// auth sharing.
106#[cfg_attr(feature = "uniffi", uniffi::export(with_foreign))]
107#[macros::async_trait]
108pub trait SessionStore: Send + Sync {
109    async fn get_session(
110        &self,
111        service_identity_key: PublicKey,
112    ) -> Result<Session, SessionStoreError>;
113
114    async fn set_session(
115        &self,
116        service_identity_key: PublicKey,
117        session: Session,
118    ) -> Result<(), SessionStoreError>;
119}