greentic_oauth_core/
verifier.rs1use std::{
2 collections::HashMap,
3 sync::Mutex,
4 time::{Duration, Instant},
5};
6
7pub trait CodeVerifierStore: Send + Sync {
9 fn put(&self, state: String, verifier: String, ttl: Duration);
10 fn take(&self, state: &str) -> Option<String>;
11}
12
13#[derive(Debug)]
14struct Entry {
15 verifier: String,
16 expires_at: Instant,
17}
18
19#[derive(Debug, Default)]
21pub struct InMemoryCodeVerifierStore {
22 entries: Mutex<HashMap<String, Entry>>,
23}
24
25impl InMemoryCodeVerifierStore {
26 pub fn new() -> Self {
27 Self {
28 entries: Mutex::new(HashMap::new()),
29 }
30 }
31
32 fn purge_expired(entries: &mut HashMap<String, Entry>) {
33 let now = Instant::now();
34 entries.retain(|_, entry| entry.expires_at > now);
35 }
36}
37
38impl CodeVerifierStore for InMemoryCodeVerifierStore {
39 fn put(&self, state: String, verifier: String, ttl: Duration) {
40 let expires_at = Instant::now() + ttl;
41 let mut guard = self.entries.lock().expect("verifier store lock");
42 Self::purge_expired(&mut guard);
43 guard.insert(
44 state,
45 Entry {
46 verifier,
47 expires_at,
48 },
49 );
50 }
51
52 fn take(&self, state: &str) -> Option<String> {
53 let mut guard = self.entries.lock().expect("verifier store lock");
54 Self::purge_expired(&mut guard);
55 guard.remove(state).and_then(|entry| {
56 if entry.expires_at > Instant::now() {
57 Some(entry.verifier)
58 } else {
59 None
60 }
61 })
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::{thread, time::Duration};
69
70 #[test]
71 fn put_and_take_roundtrip() {
72 let store = InMemoryCodeVerifierStore::new();
73 store.put(
74 "state-123".into(),
75 "verifier".into(),
76 Duration::from_secs(5),
77 );
78 assert_eq!(store.take("state-123"), Some("verifier".into()));
79 assert_eq!(store.take("state-123"), None);
80 }
81
82 #[test]
83 fn expired_entries_are_dropped() {
84 let store = InMemoryCodeVerifierStore::new();
85 store.put(
86 "state-exp".into(),
87 "verifier".into(),
88 Duration::from_millis(50),
89 );
90 thread::sleep(Duration::from_millis(70));
91 assert_eq!(store.take("state-exp"), None);
92 }
93}