alibi_core/store/stateless/
verifications.rs1use 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}