pub trait StateStore: Send + Sync {
// Required methods
fn put<'life0, 'life1, 'async_trait>(
&'life0 self,
state: &'life1 State,
pending: PendingAuthRequest,
ttl: Duration,
) -> Pin<Box<dyn Future<Output = Result<(), StateStoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn take<'life0, 'life1, 'async_trait>(
&'life0 self,
state: &'life1 State,
) -> Pin<Box<dyn Future<Output = Result<Option<PendingAuthRequest>, StateStoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
}Expand description
Atomic single-use state-machine storage.
put writes a fresh PendingAuthRequest under a State key
with substrate-enforced TTL. take atomically reads-and-deletes —
a successful take MUST guarantee no other caller can also succeed
for the same key. This is the load-bearing CSRF / state-replay
defense (Phase 11.B audit).
Substrate atomicity examples:
- Redis:
EVALwith a GET+DEL script, or Redis 6.2+GETDEL - Postgres:
DELETE FROM oidc_state WHERE state = $1 RETURNING … - KVRocks:
GETDEL(Redis-compatible 6.2+ command) - In-memory test:
tokio::sync::Mutex<HashMap>held across both ops
Required Methods§
Sourcefn put<'life0, 'life1, 'async_trait>(
&'life0 self,
state: &'life1 State,
pending: PendingAuthRequest,
ttl: Duration,
) -> Pin<Box<dyn Future<Output = Result<(), StateStoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn put<'life0, 'life1, 'async_trait>(
&'life0 self,
state: &'life1 State,
pending: PendingAuthRequest,
ttl: Duration,
) -> Pin<Box<dyn Future<Output = Result<(), StateStoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Persist pending under state with ttl. Substrate must
expire the entry server-side on TTL (no stale-state leakage).
Failure modes: substrate-down, write-rejected, etc. Surfaces as
StateStoreError in the consumer.
Sourcefn take<'life0, 'life1, 'async_trait>(
&'life0 self,
state: &'life1 State,
) -> Pin<Box<dyn Future<Output = Result<Option<PendingAuthRequest>, StateStoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn take<'life0, 'life1, 'async_trait>(
&'life0 self,
state: &'life1 State,
) -> Pin<Box<dyn Future<Output = Result<Option<PendingAuthRequest>, StateStoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Atomically read-and-delete the entry under state. Returns
None if the entry never existed, was already consumed, or
expired. The three cases are intentionally indistinguishable
to the caller — they all map to
[super::CallbackError::StateNotFoundOrConsumed].