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