mas_storage/user/mod.rs
1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2021-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
7//! Repositories to interact with entities related to user accounts
8
9use async_trait::async_trait;
10use mas_data_model::{Clock, User};
11use rand_core::RngCore;
12use ulid::Ulid;
13
14use crate::{Page, Pagination, repository_impl};
15
16mod email;
17mod password;
18mod recovery;
19mod registration;
20mod registration_token;
21mod session;
22mod terms;
23
24pub use self::{
25 email::{UserEmailFilter, UserEmailRepository},
26 password::UserPasswordRepository,
27 recovery::UserRecoveryRepository,
28 registration::UserRegistrationRepository,
29 registration_token::{UserRegistrationTokenFilter, UserRegistrationTokenRepository},
30 session::{BrowserSessionFilter, BrowserSessionRepository},
31 terms::UserTermsRepository,
32};
33
34/// The state of a user account
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum UserState {
37 /// The account is deactivated, it has the `deactivated_at` timestamp set
38 Deactivated,
39
40 /// The account is locked, it has the `locked_at` timestamp set
41 Locked,
42
43 /// The account is active
44 Active,
45}
46
47impl UserState {
48 /// Returns `true` if the user state is [`Locked`].
49 ///
50 /// [`Locked`]: UserState::Locked
51 #[must_use]
52 pub fn is_locked(&self) -> bool {
53 matches!(self, Self::Locked)
54 }
55
56 /// Returns `true` if the user state is [`Deactivated`].
57 ///
58 /// [`Deactivated`]: UserState::Deactivated
59 #[must_use]
60 pub fn is_deactivated(&self) -> bool {
61 matches!(self, Self::Deactivated)
62 }
63
64 /// Returns `true` if the user state is [`Active`].
65 ///
66 /// [`Active`]: UserState::Active
67 #[must_use]
68 pub fn is_active(&self) -> bool {
69 matches!(self, Self::Active)
70 }
71}
72
73/// Filter parameters for listing users
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
75pub struct UserFilter<'a> {
76 state: Option<UserState>,
77 can_request_admin: Option<bool>,
78 is_guest: Option<bool>,
79 _phantom: std::marker::PhantomData<&'a ()>,
80}
81
82impl UserFilter<'_> {
83 /// Create a new [`UserFilter`] with default values
84 #[must_use]
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 /// Filter for active users
90 #[must_use]
91 pub fn active_only(mut self) -> Self {
92 self.state = Some(UserState::Active);
93 self
94 }
95
96 /// Filter for locked users
97 #[must_use]
98 pub fn locked_only(mut self) -> Self {
99 self.state = Some(UserState::Locked);
100 self
101 }
102
103 /// Filter for deactivated users
104 #[must_use]
105 pub fn deactivated_only(mut self) -> Self {
106 self.state = Some(UserState::Deactivated);
107 self
108 }
109
110 /// Filter for users that can request admin privileges
111 #[must_use]
112 pub fn can_request_admin_only(mut self) -> Self {
113 self.can_request_admin = Some(true);
114 self
115 }
116
117 /// Filter for users that can't request admin privileges
118 #[must_use]
119 pub fn cannot_request_admin_only(mut self) -> Self {
120 self.can_request_admin = Some(false);
121 self
122 }
123
124 /// Filter for guest users
125 #[must_use]
126 pub fn guest_only(mut self) -> Self {
127 self.is_guest = Some(true);
128 self
129 }
130
131 /// Filter for non-guest users
132 #[must_use]
133 pub fn non_guest_only(mut self) -> Self {
134 self.is_guest = Some(false);
135 self
136 }
137
138 /// Get the state filter
139 ///
140 /// Returns [`None`] if no state filter was set
141 #[must_use]
142 pub fn state(&self) -> Option<UserState> {
143 self.state
144 }
145
146 /// Get the can request admin filter
147 ///
148 /// Returns [`None`] if no can request admin filter was set
149 #[must_use]
150 pub fn can_request_admin(&self) -> Option<bool> {
151 self.can_request_admin
152 }
153
154 /// Get the is guest filter
155 ///
156 /// Returns [`None`] if no is guest filter was set
157 #[must_use]
158 pub fn is_guest(&self) -> Option<bool> {
159 self.is_guest
160 }
161}
162
163/// A [`UserRepository`] helps interacting with [`User`] saved in the storage
164/// backend
165#[async_trait]
166pub trait UserRepository: Send + Sync {
167 /// The error type returned by the repository
168 type Error;
169
170 /// Lookup a [`User`] by its ID
171 ///
172 /// Returns `None` if no [`User`] was found
173 ///
174 /// # Parameters
175 ///
176 /// * `id`: The ID of the [`User`] to lookup
177 ///
178 /// # Errors
179 ///
180 /// Returns [`Self::Error`] if the underlying repository fails
181 async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
182
183 /// Find a [`User`] by its username, in a case-insensitive manner
184 ///
185 /// Returns `None` if no [`User`] was found
186 ///
187 /// # Parameters
188 ///
189 /// * `username`: The username of the [`User`] to lookup
190 ///
191 /// # Errors
192 ///
193 /// Returns [`Self::Error`] if the underlying repository fails
194 async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
195
196 /// Create a new [`User`]
197 ///
198 /// Returns the newly created [`User`]
199 ///
200 /// # Parameters
201 ///
202 /// * `rng`: A random number generator to generate the [`User`] ID
203 /// * `clock`: The clock used to generate timestamps
204 /// * `username`: The username of the [`User`]
205 ///
206 /// # Errors
207 ///
208 /// Returns [`Self::Error`] if the underlying repository fails
209 async fn add(
210 &mut self,
211 rng: &mut (dyn RngCore + Send),
212 clock: &dyn Clock,
213 username: String,
214 ) -> Result<User, Self::Error>;
215
216 /// Check if a [`User`] exists
217 ///
218 /// Returns `true` if the [`User`] exists, `false` otherwise
219 ///
220 /// # Parameters
221 ///
222 /// * `username`: The username of the [`User`] to lookup
223 ///
224 /// # Errors
225 ///
226 /// Returns [`Self::Error`] if the underlying repository fails
227 async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
228
229 /// Lock a [`User`]
230 ///
231 /// Returns the locked [`User`]
232 ///
233 /// # Parameters
234 ///
235 /// * `clock`: The clock used to generate timestamps
236 /// * `user`: The [`User`] to lock
237 ///
238 /// # Errors
239 ///
240 /// Returns [`Self::Error`] if the underlying repository fails
241 async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
242
243 /// Unlock a [`User`]
244 ///
245 /// Returns the unlocked [`User`]
246 ///
247 /// # Parameters
248 ///
249 /// * `user`: The [`User`] to unlock
250 ///
251 /// # Errors
252 ///
253 /// Returns [`Self::Error`] if the underlying repository fails
254 async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
255
256 /// Deactivate a [`User`]
257 ///
258 /// Returns the deactivated [`User`]
259 ///
260 /// # Parameters
261 ///
262 /// * `clock`: The clock used to generate timestamps
263 /// * `user`: The [`User`] to deactivate
264 ///
265 /// # Errors
266 ///
267 /// Returns [`Self::Error`] if the underlying repository fails
268 async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
269
270 /// Reactivate a [`User`]
271 ///
272 /// Returns the reactivated [`User`]
273 ///
274 /// # Parameters
275 ///
276 /// * `user`: The [`User`] to reactivate
277 ///
278 /// # Errors
279 ///
280 /// Returns [`Self::Error`] if the underlying repository fails
281 async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
282
283 /// Set whether a [`User`] can request admin
284 ///
285 /// Returns the [`User`] with the new `can_request_admin` value
286 ///
287 /// # Parameters
288 ///
289 /// * `user`: The [`User`] to update
290 ///
291 /// # Errors
292 ///
293 /// Returns [`Self::Error`] if the underlying repository fails
294 async fn set_can_request_admin(
295 &mut self,
296 user: User,
297 can_request_admin: bool,
298 ) -> Result<User, Self::Error>;
299
300 /// List [`User`] with the given filter and pagination
301 ///
302 /// # Parameters
303 ///
304 /// * `filter`: The filter parameters
305 /// * `pagination`: The pagination parameters
306 ///
307 /// # Errors
308 ///
309 /// Returns [`Self::Error`] if the underlying repository fails
310 async fn list(
311 &mut self,
312 filter: UserFilter<'_>,
313 pagination: Pagination,
314 ) -> Result<Page<User>, Self::Error>;
315
316 /// Count the [`User`] with the given filter
317 ///
318 /// # Parameters
319 ///
320 /// * `filter`: The filter parameters
321 ///
322 /// # Errors
323 ///
324 /// Returns [`Self::Error`] if the underlying repository fails
325 async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
326
327 /// Acquire a lock on the user to make sure device operations are done in a
328 /// sequential way. The lock is released when the repository is saved or
329 /// rolled back.
330 ///
331 /// # Parameters
332 ///
333 /// * `user`: The user to lock
334 ///
335 /// # Errors
336 ///
337 /// Returns [`Self::Error`] if the underlying repository fails
338 async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
339}
340
341repository_impl!(UserRepository:
342 async fn lookup(&mut self, id: Ulid) -> Result<Option<User>, Self::Error>;
343 async fn find_by_username(&mut self, username: &str) -> Result<Option<User>, Self::Error>;
344 async fn add(
345 &mut self,
346 rng: &mut (dyn RngCore + Send),
347 clock: &dyn Clock,
348 username: String,
349 ) -> Result<User, Self::Error>;
350 async fn exists(&mut self, username: &str) -> Result<bool, Self::Error>;
351 async fn lock(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
352 async fn unlock(&mut self, user: User) -> Result<User, Self::Error>;
353 async fn deactivate(&mut self, clock: &dyn Clock, user: User) -> Result<User, Self::Error>;
354 async fn reactivate(&mut self, user: User) -> Result<User, Self::Error>;
355 async fn set_can_request_admin(
356 &mut self,
357 user: User,
358 can_request_admin: bool,
359 ) -> Result<User, Self::Error>;
360 async fn list(
361 &mut self,
362 filter: UserFilter<'_>,
363 pagination: Pagination,
364 ) -> Result<Page<User>, Self::Error>;
365 async fn count(&mut self, filter: UserFilter<'_>) -> Result<usize, Self::Error>;
366 async fn acquire_lock_for_sync(&mut self, user: &User) -> Result<(), Self::Error>;
367);