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