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