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    /// Get all [`UserEmail`] of a [`User`]
97    ///
98    /// # Parameters
99    ///
100    /// * `user`: The [`User`] for whom to lookup the [`UserEmail`]
101    ///
102    /// # Errors
103    ///
104    /// Returns [`Self::Error`] if the underlying repository fails
105    async fn all(&mut self, user: &User) -> Result<Vec<UserEmail>, Self::Error>;
106
107    /// List [`UserEmail`] with the given filter and pagination
108    ///
109    /// # Parameters
110    ///
111    /// * `filter`: The filter parameters
112    /// * `pagination`: The pagination parameters
113    ///
114    /// # Errors
115    ///
116    /// Returns [`Self::Error`] if the underlying repository fails
117    async fn list(
118        &mut self,
119        filter: UserEmailFilter<'_>,
120        pagination: Pagination,
121    ) -> Result<Page<UserEmail>, Self::Error>;
122
123    /// Count the [`UserEmail`] with the given filter
124    ///
125    /// # Parameters
126    ///
127    /// * `filter`: The filter parameters
128    ///
129    /// # Errors
130    ///
131    /// Returns [`Self::Error`] if the underlying repository fails
132    async fn count(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
133
134    /// Create a new [`UserEmail`] for a [`User`]
135    ///
136    /// Returns the newly created [`UserEmail`]
137    ///
138    /// # Parameters
139    ///
140    /// * `rng`: The random number generator to use
141    /// * `clock`: The clock to use
142    /// * `user`: The [`User`] for whom to create the [`UserEmail`]
143    /// * `email`: The email address of the [`UserEmail`]
144    ///
145    /// # Errors
146    ///
147    /// Returns [`Self::Error`] if the underlying repository fails
148    async fn add(
149        &mut self,
150        rng: &mut (dyn RngCore + Send),
151        clock: &dyn Clock,
152        user: &User,
153        email: String,
154    ) -> Result<UserEmail, Self::Error>;
155
156    /// Delete a [`UserEmail`]
157    ///
158    /// # Parameters
159    ///
160    /// * `user_email`: The [`UserEmail`] to delete
161    ///
162    /// # Errors
163    ///
164    /// Returns [`Self::Error`] if the underlying repository fails
165    async fn remove(&mut self, user_email: UserEmail) -> Result<(), Self::Error>;
166
167    /// Delete all [`UserEmail`] with the given filter
168    ///
169    /// Returns the number of deleted [`UserEmail`]s
170    ///
171    /// # Parameters
172    ///
173    /// * `filter`: The filter parameters
174    ///
175    /// # Errors
176    ///
177    /// Returns [`Self::Error`] if the underlying repository fails
178    async fn remove_bulk(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
179
180    /// Add a new [`UserEmailAuthentication`] for a [`BrowserSession`]
181    ///
182    /// # Parameters
183    ///
184    /// * `rng`: The random number generator to use
185    /// * `clock`: The clock to use
186    /// * `email`: The email address to add
187    /// * `session`: The [`BrowserSession`] for which to add the
188    ///   [`UserEmailAuthentication`]
189    ///
190    /// # Errors
191    ///
192    /// Returns an error if the underlying repository fails
193    async fn add_authentication_for_session(
194        &mut self,
195        rng: &mut (dyn RngCore + Send),
196        clock: &dyn Clock,
197        email: String,
198        session: &BrowserSession,
199    ) -> Result<UserEmailAuthentication, Self::Error>;
200
201    /// Add a new [`UserEmailAuthentication`] for a [`UserRegistration`]
202    ///
203    /// # Parameters
204    ///
205    /// * `rng`: The random number generator to use
206    /// * `clock`: The clock to use
207    /// * `email`: The email address to add
208    /// * `registration`: The [`UserRegistration`] for which to add the
209    ///   [`UserEmailAuthentication`]
210    ///
211    /// # Errors
212    ///
213    /// Returns an error if the underlying repository fails
214    async fn add_authentication_for_registration(
215        &mut self,
216        rng: &mut (dyn RngCore + Send),
217        clock: &dyn Clock,
218        email: String,
219        registration: &UserRegistration,
220    ) -> Result<UserEmailAuthentication, Self::Error>;
221
222    /// Add a new [`UserEmailAuthenticationCode`] for a
223    /// [`UserEmailAuthentication`]
224    ///
225    /// # Parameters
226    ///
227    /// * `rng`: The random number generator to use
228    /// * `clock`: The clock to use
229    /// * `duration`: The duration for which the code is valid
230    /// * `authentication`: The [`UserEmailAuthentication`] for which to add the
231    ///   [`UserEmailAuthenticationCode`]
232    /// * `code`: The code to add
233    ///
234    /// # Errors
235    ///
236    /// Returns an error if the underlying repository fails or if the code
237    /// already exists for this session
238    async fn add_authentication_code(
239        &mut self,
240        rng: &mut (dyn RngCore + Send),
241        clock: &dyn Clock,
242        duration: chrono::Duration,
243        authentication: &UserEmailAuthentication,
244        code: String,
245    ) -> Result<UserEmailAuthenticationCode, Self::Error>;
246
247    /// Lookup a [`UserEmailAuthentication`]
248    ///
249    /// # Parameters
250    ///
251    /// * `id`: The ID of the [`UserEmailAuthentication`] to lookup
252    ///
253    /// # Errors
254    ///
255    /// Returns an error if the underlying repository fails
256    async fn lookup_authentication(
257        &mut self,
258        id: Ulid,
259    ) -> Result<Option<UserEmailAuthentication>, Self::Error>;
260
261    /// Find a [`UserEmailAuthenticationCode`] by its code and session
262    ///
263    /// # Parameters
264    ///
265    /// * `authentication`: The [`UserEmailAuthentication`] to find the code for
266    /// * `code`: The code of the [`UserEmailAuthentication`] to lookup
267    ///
268    /// # Errors
269    ///
270    /// Returns an error if the underlying repository fails
271    async fn find_authentication_code(
272        &mut self,
273        authentication: &UserEmailAuthentication,
274        code: &str,
275    ) -> Result<Option<UserEmailAuthenticationCode>, Self::Error>;
276
277    /// Complete a [`UserEmailAuthentication`] by using the given code
278    ///
279    /// Returns the completed [`UserEmailAuthentication`]
280    ///
281    /// # Parameters
282    ///
283    /// * `clock`: The clock to use to generate timestamps
284    /// * `authentication`: The [`UserEmailAuthentication`] to complete
285    /// * `code`: The [`UserEmailAuthenticationCode`] to use
286    ///
287    /// # Errors
288    ///
289    /// Returns an error if the underlying repository fails
290    async fn complete_authentication(
291        &mut self,
292        clock: &dyn Clock,
293        authentication: UserEmailAuthentication,
294        code: &UserEmailAuthenticationCode,
295    ) -> Result<UserEmailAuthentication, Self::Error>;
296}
297
298repository_impl!(UserEmailRepository:
299    async fn lookup(&mut self, id: Ulid) -> Result<Option<UserEmail>, Self::Error>;
300    async fn find(&mut self, user: &User, email: &str) -> Result<Option<UserEmail>, Self::Error>;
301
302    async fn all(&mut self, user: &User) -> Result<Vec<UserEmail>, Self::Error>;
303    async fn list(
304        &mut self,
305        filter: UserEmailFilter<'_>,
306        pagination: Pagination,
307    ) -> Result<Page<UserEmail>, Self::Error>;
308    async fn count(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
309
310    async fn add(
311        &mut self,
312        rng: &mut (dyn RngCore + Send),
313        clock: &dyn Clock,
314        user: &User,
315        email: String,
316    ) -> Result<UserEmail, Self::Error>;
317    async fn remove(&mut self, user_email: UserEmail) -> Result<(), Self::Error>;
318
319    async fn remove_bulk(&mut self, filter: UserEmailFilter<'_>) -> Result<usize, Self::Error>;
320
321    async fn add_authentication_for_session(
322        &mut self,
323        rng: &mut (dyn RngCore + Send),
324        clock: &dyn Clock,
325        email: String,
326        session: &BrowserSession,
327    ) -> Result<UserEmailAuthentication, Self::Error>;
328
329    async fn add_authentication_for_registration(
330        &mut self,
331        rng: &mut (dyn RngCore + Send),
332        clock: &dyn Clock,
333        email: String,
334        registration: &UserRegistration,
335    ) -> Result<UserEmailAuthentication, Self::Error>;
336
337    async fn add_authentication_code(
338        &mut self,
339        rng: &mut (dyn RngCore + Send),
340        clock: &dyn Clock,
341        duration: chrono::Duration,
342        authentication: &UserEmailAuthentication,
343        code: String,
344    ) -> Result<UserEmailAuthenticationCode, Self::Error>;
345
346    async fn lookup_authentication(
347        &mut self,
348        id: Ulid,
349    ) -> Result<Option<UserEmailAuthentication>, Self::Error>;
350
351    async fn find_authentication_code(
352        &mut self,
353        authentication: &UserEmailAuthentication,
354        code: &str,
355    ) -> Result<Option<UserEmailAuthenticationCode>, Self::Error>;
356
357    async fn complete_authentication(
358        &mut self,
359        clock: &dyn Clock,
360        authentication: UserEmailAuthentication,
361        code: &UserEmailAuthenticationCode,
362    ) -> Result<UserEmailAuthentication, Self::Error>;
363);