Skip to main content

StateStore

Trait StateStore 

Source
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: EVAL with 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§

Source

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.

Source

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].

Implementors§