oauth2_broker/store/
memory.rs

1//! Thread-safe in-memory [`BrokerStore`] implementation for local development and tests.
2
3// self
4use crate::{
5	_prelude::*,
6	auth::{ScopeSet, TokenFamily, TokenRecord, token::secret::TokenSecret},
7	store::{BrokerStore, CompareAndSwapOutcome, StoreError, StoreFuture, StoreKey},
8};
9
10type StoreMap = Arc<RwLock<HashMap<StoreKey, TokenRecord>>>;
11
12/// Thread-safe storage backend that keeps records in-process for tests and demos.
13#[derive(Clone, Debug, Default)]
14pub struct MemoryStore(StoreMap);
15impl MemoryStore {
16	fn save_now(map: StoreMap, record: TokenRecord) -> Result<(), StoreError> {
17		let key = StoreKey::new(&record.family, &record.scope);
18
19		map.write().insert(key, record);
20
21		Ok(())
22	}
23
24	fn fetch_now(map: StoreMap, family: TokenFamily, scope: ScopeSet) -> Option<TokenRecord> {
25		let key = StoreKey::new(&family, &scope);
26
27		map.read().get(&key).cloned()
28	}
29
30	fn cas_now(
31		map: StoreMap,
32		family: TokenFamily,
33		scope: ScopeSet,
34		expected_refresh: Option<&str>,
35		replacement: TokenRecord,
36	) -> CompareAndSwapOutcome {
37		let key = StoreKey::new(&family, &scope);
38		let mut guard = map.write();
39		let outcome = match guard.get(&key) {
40			Some(existing)
41				if Self::refresh_matches(existing.refresh_token.as_ref(), expected_refresh) =>
42				CompareAndSwapOutcome::Updated,
43			Some(_) => CompareAndSwapOutcome::RefreshMismatch,
44			None => CompareAndSwapOutcome::Missing,
45		};
46
47		if matches!(outcome, CompareAndSwapOutcome::Updated) {
48			guard.insert(key, replacement);
49		}
50
51		outcome
52	}
53
54	fn refresh_matches(current: Option<&TokenSecret>, expected: Option<&str>) -> bool {
55		match (current.map(TokenSecret::expose), expected) {
56			(None, None) => true,
57			(Some(cur), Some(exp)) => cur == exp,
58			_ => false,
59		}
60	}
61
62	fn revoke_now(
63		map: StoreMap,
64		family: TokenFamily,
65		scope: ScopeSet,
66		instant: OffsetDateTime,
67	) -> Option<TokenRecord> {
68		let key = StoreKey::new(&family, &scope);
69		let mut guard = map.write();
70
71		match guard.get_mut(&key) {
72			Some(record) => {
73				record.revoke(instant);
74
75				Some(record.clone())
76			},
77			None => None,
78		}
79	}
80}
81impl BrokerStore for MemoryStore {
82	fn save(&self, record: TokenRecord) -> StoreFuture<'_, ()> {
83		let map = self.0.clone();
84
85		Box::pin(async move { Self::save_now(map, record) })
86	}
87
88	fn fetch<'a>(
89		&'a self,
90		family: &'a TokenFamily,
91		scope: &'a ScopeSet,
92	) -> StoreFuture<'a, Option<TokenRecord>> {
93		let map = self.0.clone();
94		let family = family.to_owned();
95		let scope = scope.to_owned();
96
97		Box::pin(async move { Ok(Self::fetch_now(map, family, scope)) })
98	}
99
100	fn compare_and_swap_refresh<'a>(
101		&'a self,
102		family: &'a TokenFamily,
103		scope: &'a ScopeSet,
104		expected_refresh: Option<&'a str>,
105		replacement: TokenRecord,
106	) -> StoreFuture<'a, CompareAndSwapOutcome> {
107		let map = self.0.clone();
108		let family = family.to_owned();
109		let scope = scope.to_owned();
110
111		Box::pin(
112			async move { Ok(Self::cas_now(map, family, scope, expected_refresh, replacement)) },
113		)
114	}
115
116	fn revoke<'a>(
117		&'a self,
118		family: &'a TokenFamily,
119		scope: &'a ScopeSet,
120		instant: OffsetDateTime,
121	) -> StoreFuture<'a, Option<TokenRecord>> {
122		let map = self.0.clone();
123		let family = family.to_owned();
124		let scope = scope.to_owned();
125
126		Box::pin(async move { Ok(Self::revoke_now(map, family, scope, instant)) })
127	}
128}