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