LruOAuthRequestStorage

Struct LruOAuthRequestStorage 

Source
pub struct LruOAuthRequestStorage { /* private fields */ }
Expand description

An LRU-based implementation of OAuthRequestStorage that maintains a fixed-size cache of OAuth requests.

This storage implementation uses an LRU (Least Recently Used) cache to store OAuth requests in memory with automatic eviction of the least recently accessed entries when the cache reaches its capacity. This is ideal for scenarios where you want to cache frequently accessed OAuth requests while keeping memory usage bounded.

§Thread Safety

This implementation is thread-safe through the use of Arc<Mutex<LruCache<String, OAuthRequest>>>. All operations are protected by a mutex, ensuring safe concurrent access from multiple threads or async tasks.

§Cache Behavior

  • Get operations: Move accessed entries to the front of the LRU order
  • Insert operations: Add new entries at the front, evicting the least recently used if at capacity
  • Delete operations: Remove entries from the cache entirely
  • Capacity management: Automatically evicts least recently used entries when capacity is exceeded
  • Expiration handling: Returns only non-expired OAuth requests based on expires_at timestamp

§Use Cases

This implementation is particularly suitable for:

  • Caching OAuth authorization requests during the OAuth flow
  • Scenarios with bounded memory requirements for OAuth state management
  • Applications where some OAuth request lookup misses are acceptable
  • High-performance applications requiring fast in-memory OAuth state access
  • Stateless OAuth servers that need temporary request storage

§Limitations

  • Persistence: Data is lost when the application restarts
  • Capacity: Limited to the configured cache size
  • Cache misses: Older entries may be evicted and need OAuth flow restart
  • Memory usage: All cached OAuth data is kept in memory
  • Request size: OAuth requests with large key data consume more memory per entry

§Examples

use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

// Create an LRU cache with capacity for 1000 OAuth requests
let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(1000).unwrap());

// Create a sample OAuth request
let request = OAuthRequest {
    oauth_state: "unique-state-123".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "secure-nonce".to_string(),
    pkce_verifier: "code-verifier".to_string(),
    signing_public_key: "public-key-data".to_string(),
    dpop_private_key: "private-key-data".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};

// Store the OAuth request
storage.insert_oauth_request(request.clone()).await?;

// Retrieve the OAuth request
let retrieved = storage.get_oauth_request_by_state("unique-state-123").await?;
assert_eq!(retrieved.as_ref().map(|r| &r.oauth_state), Some(&request.oauth_state));

// Delete the OAuth request
storage.delete_oauth_request_by_state("unique-state-123").await?;
let retrieved = storage.get_oauth_request_by_state("unique-state-123").await?;
assert_eq!(retrieved, None);

§Capacity Planning

When choosing the cache capacity, consider:

  • Expected concurrent OAuth flows: Size cache to hold active OAuth requests
  • Memory constraints: Each entry uses approximately (request size + state length + overhead) bytes
  • OAuth request complexity: Requests with large key data use more memory
  • Access patterns: Higher capacity reduces cache misses for concurrent OAuth flows
  • Performance requirements: Larger caches may have slightly higher lookup times
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::num::NonZeroUsize;

// Small cache for testing or low-traffic scenarios
let small_cache = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());

// Medium cache for typical applications
let medium_cache = LruOAuthRequestStorage::new(NonZeroUsize::new(10_000).unwrap());

// Large cache for high-traffic OAuth services
let large_cache = LruOAuthRequestStorage::new(NonZeroUsize::new(100_000).unwrap());

Implementations§

Source§

impl LruOAuthRequestStorage

Source

pub fn new(capacity: NonZeroUsize) -> Self

Creates a new LruOAuthRequestStorage with the specified capacity.

The capacity determines the maximum number of OAuth requests that can be stored in the cache. When the cache reaches this capacity, the least recently used entries will be automatically evicted to make room for new entries.

§Arguments
  • capacity - The maximum number of OAuth requests to store. Must be greater than 0.
§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::num::NonZeroUsize;

// Create a cache that can hold up to 5000 OAuth requests
let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(5000).unwrap());
§Performance Considerations
  • Larger capacities provide better cache hit rates but use more memory
  • The underlying LRU implementation has O(1) access time for all operations
  • Memory usage is approximately: capacity * (average_request_size + state_size + overhead)
  • OAuth request size varies based on key data and metadata
Source

pub fn len(&self) -> usize

Returns the current number of entries in the cache.

This method provides visibility into cache usage for monitoring and debugging purposes. The count represents the current number of OAuth requests stored in the cache.

§Returns

The number of entries currently stored in the cache.

§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());
assert_eq!(storage.len(), 0);

let request = OAuthRequest {
    oauth_state: "state1".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "nonce1".to_string(),
    pkce_verifier: "verifier1".to_string(),
    signing_public_key: "pubkey1".to_string(),
    dpop_private_key: "privkey1".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(request).await?;
assert_eq!(storage.len(), 1);
Source

pub fn is_empty(&self) -> bool

Returns whether the cache is empty.

§Returns

true if the cache contains no entries, false otherwise.

§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::num::NonZeroUsize;

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());
assert!(storage.is_empty());
Source

pub fn capacity(&self) -> NonZeroUsize

Returns the maximum capacity of the cache.

This returns the capacity that was set when the cache was created and represents the maximum number of OAuth requests that can be stored before eviction occurs.

§Returns

The maximum capacity of the cache.

§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use std::num::NonZeroUsize;

let capacity = NonZeroUsize::new(500).unwrap();
let storage = LruOAuthRequestStorage::new(capacity);
assert_eq!(storage.capacity().get(), 500);
Source

pub fn clear(&self)

Clears all entries from the cache.

This method removes all OAuth requests from the cache, effectively resetting it to an empty state. This can be useful for testing or when you need to invalidate all cached OAuth data.

§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());
let request = OAuthRequest {
    oauth_state: "test-state".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "test-nonce".to_string(),
    pkce_verifier: "test-verifier".to_string(),
    signing_public_key: "test-pubkey".to_string(),
    dpop_private_key: "test-privkey".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(request).await?;
assert_eq!(storage.len(), 1);

storage.clear();
assert_eq!(storage.len(), 0);
assert!(storage.is_empty());

Trait Implementations§

Source§

impl Clone for LruOAuthRequestStorage

Source§

fn clone(&self) -> LruOAuthRequestStorage

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl OAuthRequestStorage for LruOAuthRequestStorage

Source§

fn get_oauth_request_by_state<'life0, 'life1, 'async_trait>( &'life0 self, state: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Option<OAuthRequest>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Retrieves an OAuth request by its state parameter from the LRU cache.

This method looks up an OAuth authorization request using the state parameter that is currently cached. If the state is found in the cache, the entry is moved to the front of the LRU order (marking it as recently used) and the request is returned.

Expired requests (where expires_at < current_time) are automatically filtered out and not returned, ensuring only valid OAuth requests are accessible.

§Arguments
  • state - The OAuth state parameter to look up in the cache
§Returns
  • Ok(Some(request)) - If the state is found in the cache and not expired
  • Ok(None) - If the state is not found in the cache or the request has expired
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior

When a request is successfully retrieved, it’s marked as recently used in the LRU order, making it less likely to be evicted in future operations. Expired requests are treated as cache misses.

§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());

// Cache miss - state not in cache
let request = storage.get_oauth_request_by_state("unknown-state").await?;
assert_eq!(request, None);

// Add request to cache
let oauth_req = OAuthRequest {
    oauth_state: "valid-state-123".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "secure-nonce".to_string(),
    pkce_verifier: "code-verifier".to_string(),
    signing_public_key: "public-key-data".to_string(),
    dpop_private_key: "private-key-data".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(oauth_req.clone()).await?;

// Cache hit - state found in cache
let request = storage.get_oauth_request_by_state("valid-state-123").await?;
assert_eq!(request.as_ref().map(|r| &r.oauth_state), Some(&oauth_req.oauth_state));
Source§

fn delete_oauth_request_by_state<'life0, 'life1, 'async_trait>( &'life0 self, state: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Deletes an OAuth request from the LRU cache by its state parameter.

This method removes an OAuth authorization request from the cache using its state parameter. If the state exists in the cache, it is removed entirely, freeing up space for new entries.

§Arguments
  • state - The OAuth state parameter identifying the request to delete
§Returns
  • Ok(()) - If the OAuth request was successfully deleted or didn’t exist
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior
  • If the state exists in the cache, it is removed completely
  • If the state doesn’t exist, the operation succeeds without error
  • Removing entries frees up capacity for new entries
§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());

// Add an OAuth request
let request = OAuthRequest {
    oauth_state: "deletable-state".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "test-nonce".to_string(),
    pkce_verifier: "test-verifier".to_string(),
    signing_public_key: "test-pubkey".to_string(),
    dpop_private_key: "test-privkey".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(request).await?;
let retrieved = storage.get_oauth_request_by_state("deletable-state").await?;
assert!(retrieved.is_some());

// Delete the request
storage.delete_oauth_request_by_state("deletable-state").await?;
let retrieved = storage.get_oauth_request_by_state("deletable-state").await?;
assert_eq!(retrieved, None);

// Deleting non-existent entry is safe
storage.delete_oauth_request_by_state("non-existent-state").await?;
Source§

fn insert_oauth_request<'life0, 'async_trait>( &'life0 self, request: OAuthRequest, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Inserts a new OAuth request into the LRU cache.

This method stores an OAuth authorization request in the cache. If a request with the same state already exists in the cache, it is replaced and the entry is moved to the front of the LRU order. If the state is new and the cache is at capacity, the least recently used entry is evicted to make room.

§Arguments
  • request - The complete OAuth request to store. The request’s oauth_state field will be used as the storage key.
§Returns
  • Ok(()) - If the OAuth request was successfully stored
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior
  • If the cache is at capacity and this is a new state, the least recently used entry is evicted
  • The new or updated entry is placed at the front of the LRU order
  • Existing entries with the same state are replaced in place
§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(2).unwrap()); // Small cache for demo

// Add first request
let req1 = OAuthRequest {
    oauth_state: "state1".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "nonce1".to_string(),
    pkce_verifier: "verifier1".to_string(),
    signing_public_key: "pubkey1".to_string(),
    dpop_private_key: "privkey1".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(req1).await?;
assert_eq!(storage.len(), 1);

// Add second request
let req2 = OAuthRequest {
    oauth_state: "state2".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "nonce2".to_string(),
    pkce_verifier: "verifier2".to_string(),
    signing_public_key: "pubkey2".to_string(),
    dpop_private_key: "privkey2".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(req2).await?;
assert_eq!(storage.len(), 2);

// Add third request - this will evict the least recently used entry (state1)
let req3 = OAuthRequest {
    oauth_state: "state3".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "nonce3".to_string(),
    pkce_verifier: "verifier3".to_string(),
    signing_public_key: "pubkey3".to_string(),
    dpop_private_key: "privkey3".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10),
};
storage.insert_oauth_request(req3).await?;
assert_eq!(storage.len(), 2); // Still at capacity

// state1 should be evicted
let request = storage.get_oauth_request_by_state("state1").await?;
assert_eq!(request, None);

// state2 and state3 should still be present
let req2_retrieved = storage.get_oauth_request_by_state("state2").await?;
let req3_retrieved = storage.get_oauth_request_by_state("state3").await?;
assert!(req2_retrieved.is_some());
assert!(req3_retrieved.is_some());
Source§

fn clear_expired_oauth_requests<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<u64>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Clears all expired OAuth requests from the LRU cache.

This method performs cleanup by removing OAuth requests that have passed their expiration time (expires_at <= current_time). This is important for maintaining security and preventing storage bloat from abandoned OAuth flows.

§Returns
  • Ok(count) - The number of expired requests that were successfully removed
  • Err(error) - If an error occurs (primarily mutex poisoning, which is very rare)
§Cache Behavior
  • Compares each request’s expires_at against the current time (Utc::now())
  • Removes all requests where expires_at <= current_time
  • Maintains LRU order for remaining valid requests
  • Frees up capacity for new OAuth requests
§Examples
use atproto_oauth::storage_lru::LruOAuthRequestStorage;
use atproto_oauth::storage::OAuthRequestStorage;
use atproto_oauth::workflow::OAuthRequest;
use std::num::NonZeroUsize;
use chrono::{Utc, Duration};

let storage = LruOAuthRequestStorage::new(NonZeroUsize::new(100).unwrap());

// Add a request that will expire soon
let expired_request = OAuthRequest {
    oauth_state: "soon-expired".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "nonce1".to_string(),
    pkce_verifier: "verifier1".to_string(),
    signing_public_key: "pubkey1".to_string(),
    dpop_private_key: "privkey1".to_string(),
    created_at: Utc::now() - Duration::minutes(20),
    expires_at: Utc::now() - Duration::minutes(10), // Already expired
};
storage.insert_oauth_request(expired_request).await?;

// Add a valid request
let valid_request = OAuthRequest {
    oauth_state: "still-valid".to_string(),
    issuer: "https://pds.example.com".to_string(),
    authorization_server: "https://pds.example.com".to_string(),
    nonce: "nonce2".to_string(),
    pkce_verifier: "verifier2".to_string(),
    signing_public_key: "pubkey2".to_string(),
    dpop_private_key: "privkey2".to_string(),
    created_at: Utc::now(),
    expires_at: Utc::now() + Duration::minutes(10), // Still valid
};
storage.insert_oauth_request(valid_request).await?;

assert_eq!(storage.len(), 2);

// Clean up expired requests
let removed_count = storage.clear_expired_oauth_requests().await?;
assert_eq!(removed_count, 1); // One expired request removed
assert_eq!(storage.len(), 1); // One valid request remains

// Verify the valid request is still accessible
let remaining = storage.get_oauth_request_by_state("still-valid").await?;
assert!(remaining.is_some());

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,