mas_storage/user/email.rs
1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use async_trait::async_trait;
8use mas_data_model::{
9 BrowserSession, User, UserEmail, UserEmailAuthentication, UserEmailAuthenticationCode,
10 UserRegistration,
11};
12use rand_core::RngCore;
13use ulid::Ulid;
14
15use crate::{Clock, Pagination, pagination::Page, repository_impl};
16
17/// Filter parameters for listing user emails
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
19pub struct UserEmailFilter<'a> {
20 user: Option<&'a User>,
21 email: Option<&'a str>,
22}
23
24impl<'a> UserEmailFilter<'a> {
25 /// Create a new [`UserEmailFilter`] with default values
26 #[must_use]
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 /// Filter for emails of a specific user
32 #[must_use]
33 pub fn for_user(mut self, user: &'a User) -> Self {
34 self.user = Some(user);
35 self
36 }
37
38 /// Filter for emails matching a specific email address
39 #[must_use]
40 pub fn for_email(mut self, email: &'a str) -> Self {
41 self.email = Some(email);
42 self
43 }
44
45 /// Get the user filter
46 ///
47 /// Returns [`None`] if no user filter is set
48 #[must_use]
49 pub fn user(&self) -> Option<&User> {
50 self.user
51 }
52
53 /// Get the email filter
54 ///
55 /// Returns [`None`] if no email filter is set
56 #[must_use]
57 pub fn email(&self) -> Option<&str> {
58 self.email
59 }
60}
61
62/// A [`UserEmailRepository`] helps interacting with [`UserEmail`] saved in the
63/// storage backend
64#[async_trait]
65pub trait UserEmailRepository: Send + Sync {
66 /// The error type returned by the repository
67 type Error;
68
69 /// Lookup an [`UserEmail`] by its ID
70 ///
71 /// Returns `None` if no [`UserEmail`] was found
72 ///
73 /// # Parameters
74 ///
75 /// * `id`: The ID of the [`UserEmail`] to lookup
76 ///
77 /// # Errors
78 ///
79 /// Returns [`Self::Error`] if the underlying repository fails
80 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserEmail>, Self::Error>;
81
82 /// Lookup an [`UserEmail`] by its email address for a [`User`]
83 ///
84 /// Returns `None` if no matching [`UserEmail`] was found
85 ///
86 /// # Parameters
87 ///
88 /// * `user`: The [`User`] for whom to lookup the [`UserEmail`]
89 /// * `email`: The email address to lookup
90 ///
91 /// # Errors
92 ///
93 /// Returns [`Self::Error`] if the underlying repository fails
94 async fn find(&mut self, user: &User, email: &str) -> Result<Option<UserEmail>, Self::Error>;
95
96 /// Lookup an [`UserEmail`] by its email address
97 ///
98 /// Returns `None` if no matching [`UserEmail`] was found or if multiple
99 /// [`UserEmail`] are found
100 ///
101 /// # Parameters
102 /// * `email`: The email address to lookup
103 ///
104 /// # Errors
105 ///
106 /// Returns [`Self::Error`] if the underlying repository fails
107 async fn find_by_email(&mut self, email: &str) -> Result<Option<UserEmail>, Self::Error>;
108
109 /// Get all [`UserEmail`] of a [`User`]
110 ///
111 /// # Parameters
112 ///
113 /// * `user`: The [`User`] for whom to lookup the [`UserEmail`]
114 ///
115 /// # Errors
116 ///
117 /// Returns [`Self::Error`] if the underlying repository fails
118 async fn all(&mut self, user: &User) -> Result<Vec<UserEmail>, Self::Error>;
119
120 /// List [`UserEmail`] with the given filter and pagination
121 ///
122 /// # Parameters
123 ///
124 /// * `filter`: The filter parameters
125 /// * `pagination`: The pagination parameters
126 ///
127 /// # Errors
128 ///
129 /// Returns [`Self::Error`] if the underlying repository fails
130 async fn list(
131 &mut self,
132 filter: UserEmailFilter<'_>,
133 pagination: Pagination,
134 ) -> Result<Page<UserEmail>, Self::Error>;
135
136 /// Count the [`UserEmail`] with the given filter
137 ///
138 /// # Parameters
139 ///
140 /// * `filter`: The filter parameters
141 ///
142 /// # Errors
143 ///
144 /// Returns [`Self::Error`] if the underlying repository fails
145 async fn count(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
146
147 /// Create a new [`UserEmail`] for a [`User`]
148 ///
149 /// Returns the newly created [`UserEmail`]
150 ///
151 /// # Parameters
152 ///
153 /// * `rng`: The random number generator to use
154 /// * `clock`: The clock to use
155 /// * `user`: The [`User`] for whom to create the [`UserEmail`]
156 /// * `email`: The email address of the [`UserEmail`]
157 ///
158 /// # Errors
159 ///
160 /// Returns [`Self::Error`] if the underlying repository fails
161 async fn add(
162 &mut self,
163 rng: &mut (dyn RngCore + Send),
164 clock: &dyn Clock,
165 user: &User,
166 email: String,
167 ) -> Result<UserEmail, Self::Error>;
168
169 /// Delete a [`UserEmail`]
170 ///
171 /// # Parameters
172 ///
173 /// * `user_email`: The [`UserEmail`] to delete
174 ///
175 /// # Errors
176 ///
177 /// Returns [`Self::Error`] if the underlying repository fails
178 async fn remove(&mut self, user_email: UserEmail) -> Result<(), Self::Error>;
179
180 /// Delete all [`UserEmail`] with the given filter
181 ///
182 /// Returns the number of deleted [`UserEmail`]s
183 ///
184 /// # Parameters
185 ///
186 /// * `filter`: The filter parameters
187 ///
188 /// # Errors
189 ///
190 /// Returns [`Self::Error`] if the underlying repository fails
191 async fn remove_bulk(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
192
193 /// Add a new [`UserEmailAuthentication`] for a [`BrowserSession`]
194 ///
195 /// # Parameters
196 ///
197 /// * `rng`: The random number generator to use
198 /// * `clock`: The clock to use
199 /// * `email`: The email address to add
200 /// * `session`: The [`BrowserSession`] for which to add the
201 /// [`UserEmailAuthentication`]
202 ///
203 /// # Errors
204 ///
205 /// Returns an error if the underlying repository fails
206 async fn add_authentication_for_session(
207 &mut self,
208 rng: &mut (dyn RngCore + Send),
209 clock: &dyn Clock,
210 email: String,
211 session: &BrowserSession,
212 ) -> Result<UserEmailAuthentication, Self::Error>;
213
214 /// Add a new [`UserEmailAuthentication`] for a [`UserRegistration`]
215 ///
216 /// # Parameters
217 ///
218 /// * `rng`: The random number generator to use
219 /// * `clock`: The clock to use
220 /// * `email`: The email address to add
221 /// * `registration`: The [`UserRegistration`] for which to add the
222 /// [`UserEmailAuthentication`]
223 ///
224 /// # Errors
225 ///
226 /// Returns an error if the underlying repository fails
227 async fn add_authentication_for_registration(
228 &mut self,
229 rng: &mut (dyn RngCore + Send),
230 clock: &dyn Clock,
231 email: String,
232 registration: &UserRegistration,
233 ) -> Result<UserEmailAuthentication, Self::Error>;
234
235 /// Add a new [`UserEmailAuthenticationCode`] for a
236 /// [`UserEmailAuthentication`]
237 ///
238 /// # Parameters
239 ///
240 /// * `rng`: The random number generator to use
241 /// * `clock`: The clock to use
242 /// * `duration`: The duration for which the code is valid
243 /// * `authentication`: The [`UserEmailAuthentication`] for which to add the
244 /// [`UserEmailAuthenticationCode`]
245 /// * `code`: The code to add
246 ///
247 /// # Errors
248 ///
249 /// Returns an error if the underlying repository fails or if the code
250 /// already exists for this session
251 async fn add_authentication_code(
252 &mut self,
253 rng: &mut (dyn RngCore + Send),
254 clock: &dyn Clock,
255 duration: chrono::Duration,
256 authentication: &UserEmailAuthentication,
257 code: String,
258 ) -> Result<UserEmailAuthenticationCode, Self::Error>;
259
260 /// Lookup a [`UserEmailAuthentication`]
261 ///
262 /// # Parameters
263 ///
264 /// * `id`: The ID of the [`UserEmailAuthentication`] to lookup
265 ///
266 /// # Errors
267 ///
268 /// Returns an error if the underlying repository fails
269 async fn lookup_authentication(
270 &mut self,
271 id: Ulid,
272 ) -> Result<Option<UserEmailAuthentication>, Self::Error>;
273
274 /// Find a [`UserEmailAuthenticationCode`] by its code and session
275 ///
276 /// # Parameters
277 ///
278 /// * `authentication`: The [`UserEmailAuthentication`] to find the code for
279 /// * `code`: The code of the [`UserEmailAuthentication`] to lookup
280 ///
281 /// # Errors
282 ///
283 /// Returns an error if the underlying repository fails
284 async fn find_authentication_code(
285 &mut self,
286 authentication: &UserEmailAuthentication,
287 code: &str,
288 ) -> Result<Option<UserEmailAuthenticationCode>, Self::Error>;
289
290 /// Complete a [`UserEmailAuthentication`] by using the given code
291 ///
292 /// Returns the completed [`UserEmailAuthentication`]
293 ///
294 /// # Parameters
295 ///
296 /// * `clock`: The clock to use to generate timestamps
297 /// * `authentication`: The [`UserEmailAuthentication`] to complete
298 /// * `code`: The [`UserEmailAuthenticationCode`] to use
299 ///
300 /// # Errors
301 ///
302 /// Returns an error if the underlying repository fails
303 async fn complete_authentication(
304 &mut self,
305 clock: &dyn Clock,
306 authentication: UserEmailAuthentication,
307 code: &UserEmailAuthenticationCode,
308 ) -> Result<UserEmailAuthentication, Self::Error>;
309}
310
311repository_impl!(UserEmailRepository:
312 async fn lookup(&mut self, id: Ulid) -> Result<Option<UserEmail>, Self::Error>;
313 async fn find(&mut self, user: &User, email: &str) -> Result<Option<UserEmail>, Self::Error>;
314 async fn find_by_email(&mut self, email: &str) -> Result<Option<UserEmail>, Self::Error>;
315
316 async fn all(&mut self, user: &User) -> Result<Vec<UserEmail>, Self::Error>;
317 async fn list(
318 &mut self,
319 filter: UserEmailFilter<'_>,
320 pagination: Pagination,
321 ) -> Result<Page<UserEmail>, Self::Error>;
322 async fn count(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
323
324 async fn add(
325 &mut self,
326 rng: &mut (dyn RngCore + Send),
327 clock: &dyn Clock,
328 user: &User,
329 email: String,
330 ) -> Result<UserEmail, Self::Error>;
331 async fn remove(&mut self, user_email: UserEmail) -> Result<(), Self::Error>;
332
333 async fn remove_bulk(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
334
335 async fn add_authentication_for_session(
336 &mut self,
337 rng: &mut (dyn RngCore + Send),
338 clock: &dyn Clock,
339 email: String,
340 session: &BrowserSession,
341 ) -> Result<UserEmailAuthentication, Self::Error>;
342
343 async fn add_authentication_for_registration(
344 &mut self,
345 rng: &mut (dyn RngCore + Send),
346 clock: &dyn Clock,
347 email: String,
348 registration: &UserRegistration,
349 ) -> Result<UserEmailAuthentication, Self::Error>;
350
351 async fn add_authentication_code(
352 &mut self,
353 rng: &mut (dyn RngCore + Send),
354 clock: &dyn Clock,
355 duration: chrono::Duration,
356 authentication: &UserEmailAuthentication,
357 code: String,
358 ) -> Result<UserEmailAuthenticationCode, Self::Error>;
359
360 async fn lookup_authentication(
361 &mut self,
362 id: Ulid,
363 ) -> Result<Option<UserEmailAuthentication>, Self::Error>;
364
365 async fn find_authentication_code(
366 &mut self,
367 authentication: &UserEmailAuthentication,
368 code: &str,
369 ) -> Result<Option<UserEmailAuthenticationCode>, Self::Error>;
370
371 async fn complete_authentication(
372 &mut self,
373 clock: &dyn Clock,
374 authentication: UserEmailAuthentication,
375 code: &UserEmailAuthenticationCode,
376 ) -> Result<UserEmailAuthentication, Self::Error>;
377);