Skip to main content

alibi_core/store/stateless/
verifications.rs

1use crate::store::VerificationStore;
2use crate::store::stateless::{StatelessSchema, StatelessStore};
3use crate::{AuthError, AuthResult, CreateVerification, VerificationView};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6#[async_trait]
7impl VerificationStore<StatelessSchema> for StatelessStore {
8    async fn create_verification_record(
9        &self,
10        data: crate::verification::VerificationCreation,
11        publication: crate::verification::VerificationPublication,
12    ) -> AuthResult<Option<crate::verification::VerificationSnapshot>> {
13        let snapshot = if publication.store_in_database {
14            let model = VerificationView {
15                id: data.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
16                identifier: data.identifier,
17                value: data.value,
18                expires_at: data.expires_at,
19                created_at: data.created_at,
20                updated_at: data.updated_at,
21            };
22            let mut state = self.lock()?;
23            if state.verifications.contains_key(&model.id) {
24                return Err(AuthError::internal("duplicate verification primary ID"));
25            }
26            _ = state.verifications.insert(model.id.clone(), model.clone());
27            crate::verification::VerificationSnapshot::from_model(&model)
28        } else {
29            data.snapshot()
30        };
31        publication.publish(&snapshot).await?;
32        Ok(Some(snapshot))
33    }
34
35    async fn consume_verification_snapshot(
36        &self,
37        identifier: &str,
38    ) -> AuthResult<Option<VerificationView>> {
39        let mut state = self.lock()?;
40        let found = state
41            .verifications
42            .values()
43            .filter(|verification| verification.identifier == identifier)
44            .max_by_key(|verification| verification.created_at)
45            .cloned();
46        state
47            .verifications
48            .retain(|_, sibling| sibling.identifier != identifier);
49        Ok(found)
50    }
51
52    async fn update_verification_by_identifier(
53        &self,
54        identifier: &str,
55        data: crate::UpdateVerification,
56    ) -> AuthResult<Option<crate::verification::VerificationSnapshot>> {
57        let mut state = self.lock()?;
58        let mut found = None;
59        for model in state.verifications.values_mut() {
60            if model.identifier == identifier {
61                model.updated_at = Utc::now();
62                if let Some(value) = &data.value {
63                    model.value.clone_from(value);
64                }
65                if let Some(expiry) = data.expires_at {
66                    model.expires_at = expiry;
67                }
68                if found.is_none() {
69                    found = Some(crate::verification::VerificationSnapshot::from_model(model));
70                }
71            }
72        }
73        Ok(found)
74    }
75
76    async fn reserve_verification_record(
77        &self,
78        logical_identifier: &str,
79        data: CreateVerification,
80    ) -> AuthResult<Option<VerificationView>> {
81        let (id, _) = crate::store::verification_reservation_key(logical_identifier);
82        let mut state = self.lock()?;
83        let indexmap::map::Entry::Vacant(entry) = state.verifications.entry(id.clone()) else {
84            return Ok(None);
85        };
86        let now = Utc::now();
87        let model = VerificationView {
88            id,
89            identifier: data.identifier,
90            value: data.value,
91            expires_at: data.expires_at,
92            created_at: now,
93            updated_at: now,
94        };
95        _ = entry.insert(model.clone());
96        Ok(Some(model))
97    }
98
99    async fn create_verification(
100        &self,
101        verification: CreateVerification,
102    ) -> AuthResult<VerificationView> {
103        let now = Utc::now();
104        let verification = VerificationView {
105            id: uuid::Uuid::new_v4().to_string(),
106            identifier: verification.identifier,
107            value: verification.value,
108            expires_at: verification.expires_at,
109            created_at: now,
110            updated_at: now,
111        };
112        _ = self
113            .lock()?
114            .verifications
115            .insert(verification.id.clone(), verification.clone());
116        Ok(verification)
117    }
118
119    async fn get_verification(
120        &self,
121        identifier: &str,
122        value: &str,
123    ) -> AuthResult<Option<VerificationView>> {
124        Ok(self
125            .lock()?
126            .verifications
127            .values()
128            .filter(|verification| {
129                verification.identifier == identifier
130                    && verification.value == value
131                    && verification.expires_at >= Utc::now()
132            })
133            .max_by_key(|verification| verification.created_at)
134            .cloned())
135    }
136
137    async fn get_verification_by_value(&self, value: &str) -> AuthResult<Option<VerificationView>> {
138        Ok(self
139            .lock()?
140            .verifications
141            .values()
142            .filter(|verification| {
143                verification.value == value && verification.expires_at >= Utc::now()
144            })
145            .max_by_key(|verification| verification.created_at)
146            .cloned())
147    }
148
149    async fn get_verification_by_identifier(
150        &self,
151        identifier: &str,
152    ) -> AuthResult<Option<VerificationView>> {
153        Ok(self
154            .lock()?
155            .verifications
156            .values()
157            .filter(|verification| {
158                verification.identifier == identifier && verification.expires_at >= Utc::now()
159            })
160            .max_by_key(|verification| verification.created_at)
161            .cloned())
162    }
163
164    async fn consume_verification(
165        &self,
166        identifier: &str,
167        value: &str,
168    ) -> AuthResult<Option<VerificationView>> {
169        let mut state = self.lock()?;
170        let found = state
171            .verifications
172            .values()
173            .filter(|verification| verification.identifier == identifier)
174            .max_by_key(|verification| verification.created_at)
175            .cloned();
176        if let Some(verification) = &found {
177            if verification.value != value {
178                return Ok(None);
179            }
180            state
181                .verifications
182                .retain(|_, sibling| sibling.identifier != identifier);
183        }
184        Ok(found.filter(|verification| verification.expires_at >= Utc::now()))
185    }
186
187    async fn get_latest_verification_by_identifier(
188        &self,
189        identifier: &str,
190    ) -> AuthResult<Option<VerificationView>> {
191        Ok(self
192            .lock()?
193            .verifications
194            .values()
195            .filter(|verification| verification.identifier == identifier)
196            .max_by_key(|verification| verification.created_at)
197            .cloned())
198    }
199
200    async fn consume_verification_by_identifier(
201        &self,
202        identifier: &str,
203    ) -> AuthResult<Option<VerificationView>> {
204        let mut state = self.lock()?;
205        let found = state
206            .verifications
207            .values()
208            .filter(|verification| verification.identifier == identifier)
209            .max_by_key(|verification| verification.created_at)
210            .cloned();
211        state
212            .verifications
213            .retain(|_, sibling| sibling.identifier != identifier);
214        Ok(found.filter(|verification| verification.expires_at >= Utc::now()))
215    }
216
217    async fn delete_verifications_by_identifier(&self, identifier: &str) -> AuthResult<()> {
218        self.lock()?
219            .verifications
220            .retain(|_, verification| verification.identifier != identifier);
221        Ok(())
222    }
223
224    async fn compare_and_swap_verification(
225        &self,
226        id: &str,
227        expected_value: &str,
228        value: &str,
229        expires_at: DateTime<Utc>,
230    ) -> AuthResult<bool> {
231        let mut state = self.lock()?;
232        let Some(verification) = state.verifications.get_mut(id) else {
233            return Ok(false);
234        };
235        if verification.value != expected_value {
236            return Ok(false);
237        }
238        value.clone_into(&mut verification.value);
239        verification.expires_at = expires_at;
240        verification.updated_at = Utc::now();
241        Ok(true)
242    }
243
244    async fn reserve_verification(&self, verification: CreateVerification) -> AuthResult<bool> {
245        let (id, _) = crate::store::verification_reservation_key(&verification.identifier);
246        let mut state = self.lock()?;
247        let indexmap::map::Entry::Vacant(entry) = state.verifications.entry(id.clone()) else {
248            return Ok(false);
249        };
250
251        let now = Utc::now();
252        _ = entry.insert(VerificationView {
253            id,
254            identifier: verification.identifier,
255            value: verification.value,
256            expires_at: verification.expires_at,
257            created_at: now,
258            updated_at: now,
259        });
260        Ok(true)
261    }
262
263    async fn delete_verification(&self, id: &str) -> AuthResult<()> {
264        _ = self.lock()?.verifications.shift_remove(id);
265        Ok(())
266    }
267
268    async fn delete_expired_verifications(&self) -> AuthResult<usize> {
269        let now = Utc::now();
270        let mut state = self.lock()?;
271        let before = state.verifications.len();
272        state.verifications.retain(|_, verification| {
273            verification.expires_at.timestamp_millis() >= now.timestamp_millis()
274        });
275        Ok(before - state.verifications.len())
276    }
277}