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);